diff --git a/client/src/App.tsx b/client/src/App.tsx index 91cbd05..cad758f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -2,12 +2,20 @@ import { Switch, Route } from "wouter"; import { queryClient } from "./lib/queryClient"; import { QueryClientProvider } from "@tanstack/react-query"; import { Toaster } from "@/components/ui/toaster"; +import { AuthProvider } from "@/lib/auth"; import NotFound from "@/pages/not-found"; import Home from "@/pages/home"; import Passport from "@/pages/passport"; import Terminal from "@/pages/terminal"; import Dashboard from "@/pages/dashboard"; import Curriculum from "@/pages/curriculum"; +import Login from "@/pages/login"; +import Admin from "@/pages/admin"; +import Pitch from "@/pages/pitch"; +import AdminArchitects from "@/pages/admin-architects"; +import AdminProjects from "@/pages/admin-projects"; +import AdminCredentials from "@/pages/admin-credentials"; +import AdminAegis from "@/pages/admin-aegis"; function Router() { return ( @@ -17,6 +25,13 @@ function Router() { + + + + + + + ); @@ -25,8 +40,10 @@ function Router() { function App() { return ( - - + + + + ); } diff --git a/client/src/lib/auth.tsx b/client/src/lib/auth.tsx new file mode 100644 index 0000000..682dd75 --- /dev/null +++ b/client/src/lib/auth.tsx @@ -0,0 +1,89 @@ +import { createContext, useContext, useState, useEffect, ReactNode } from "react"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; + +interface User { + id: string; + username: string; + isAdmin: boolean; +} + +interface AuthContextType { + user: User | null; + isLoading: boolean; + isAuthenticated: boolean; + isAdmin: boolean; + login: (username: string, password: string) => Promise; + logout: () => Promise; +} + +const AuthContext = createContext(null); + +export function AuthProvider({ children }: { children: ReactNode }) { + const queryClient = useQueryClient(); + + const { data: session, isLoading } = useQuery({ + queryKey: ["session"], + queryFn: async () => { + const res = await fetch("/api/auth/session"); + return res.json(); + }, + }); + + const loginMutation = useMutation({ + mutationFn: async ({ username, password }: { username: string; password: string }) => { + const res = await fetch("/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || "Login failed"); + } + return res.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["session"] }); + }, + }); + + const logoutMutation = useMutation({ + mutationFn: async () => { + await fetch("/api/auth/logout", { method: "POST" }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["session"] }); + }, + }); + + const login = async (username: string, password: string) => { + await loginMutation.mutateAsync({ username, password }); + }; + + const logout = async () => { + await logoutMutation.mutateAsync(); + }; + + const value: AuthContextType = { + user: session?.authenticated ? session.user : null, + isLoading, + isAuthenticated: !!session?.authenticated, + isAdmin: session?.user?.isAdmin || false, + login, + logout, + }; + + return ( + + {children} + + ); +} + +export function useAuth() { + const context = useContext(AuthContext); + if (!context) { + throw new Error("useAuth must be used within an AuthProvider"); + } + return context; +} diff --git a/client/src/pages/admin-aegis.tsx b/client/src/pages/admin-aegis.tsx new file mode 100644 index 0000000..4aa54b7 --- /dev/null +++ b/client/src/pages/admin-aegis.tsx @@ -0,0 +1,194 @@ +import { useEffect } from "react"; +import { Link, useLocation } from "wouter"; +import { useAuth } from "@/lib/auth"; +import { + Users, FileCode, Shield, Activity, LogOut, + BarChart3, User, AlertTriangle, CheckCircle, XCircle, Eye +} from "lucide-react"; + +export default function AdminAegis() { + const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth(); + const [, setLocation] = useLocation(); + + useEffect(() => { + if (!authLoading && !isAuthenticated) { + setLocation("/login"); + } + }, [authLoading, isAuthenticated, setLocation]); + + if (authLoading || !isAuthenticated) { + return ( +
+
Loading...
+
+ ); + } + + const handleLogout = async () => { + await logout(); + setLocation("/"); + }; + + // Mock threat data for demo + const mockThreats = [ + { id: 1, type: "PII Exposure", severity: "high", status: "blocked", timestamp: "2 min ago" }, + { id: 2, type: "Suspicious Pattern", severity: "medium", status: "flagged", timestamp: "15 min ago" }, + { id: 3, type: "Rate Limit", severity: "low", status: "allowed", timestamp: "1 hour ago" }, + ]; + + return ( +
+ {/* Sidebar */} +
+
+

+ AeThex +

+

Command Center

+
+ + + +
+
+
+ +
+
+
{user?.username}
+
+ {user?.isAdmin ? "Administrator" : "Member"} +
+
+
+ +
+
+ + {/* Main Content */} +
+
+
+
+

+ Aegis Monitor +

+

+ Real-time security monitoring and threat intervention +

+
+
+ + Shield Active +
+
+ + {/* Stats */} +
+
+
Threats Blocked
+
247
+
Last 24 hours
+
+
+
PII Scrubbed
+
1,892
+
Instances protected
+
+
+
Active Sessions
+
34
+
Being monitored
+
+
+
Uptime
+
99.9%
+
Last 30 days
+
+
+ + {/* Recent Activity */} +
+

+ + Recent Threat Activity +

+ +
+ {mockThreats.map((threat) => ( +
+
+
+ +
+
+
{threat.type}
+
{threat.timestamp}
+
+
+
+ + {threat.status} + + +
+
+ ))} +
+ +
+

+ Aegis security layer is monitoring all active sessions +

+
+
+
+
+
+ ); +} + +function NavItem({ icon, label, href, active = false }: { + icon: React.ReactNode; + label: string; + href: string; + active?: boolean; +}) { + return ( + +
+ {icon} + {label} +
+ + ); +} diff --git a/client/src/pages/admin-architects.tsx b/client/src/pages/admin-architects.tsx new file mode 100644 index 0000000..8bd7751 --- /dev/null +++ b/client/src/pages/admin-architects.tsx @@ -0,0 +1,354 @@ +import { useEffect, useState } from "react"; +import { motion } from "framer-motion"; +import { Link, useLocation } from "wouter"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useAuth } from "@/lib/auth"; +import { + Users, FileCode, Shield, Activity, LogOut, + Home, BarChart3, Settings, User, Search, + CheckCircle, XCircle, Eye, Edit, ChevronRight +} from "lucide-react"; + +export default function AdminArchitects() { + const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth(); + const [, setLocation] = useLocation(); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedProfile, setSelectedProfile] = useState(null); + const queryClient = useQueryClient(); + + useEffect(() => { + if (!authLoading && !isAuthenticated) { + setLocation("/login"); + } + }, [authLoading, isAuthenticated, setLocation]); + + const { data: profiles, isLoading } = useQuery({ + queryKey: ["profiles"], + queryFn: async () => { + const res = await fetch("/api/profiles"); + return res.json(); + }, + enabled: isAuthenticated, + }); + + const updateProfileMutation = useMutation({ + mutationFn: async ({ id, updates }: { id: string; updates: any }) => { + const res = await fetch(`/api/profiles/${id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(updates), + }); + if (!res.ok) throw new Error("Failed to update"); + return res.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["profiles"] }); + setSelectedProfile(null); + }, + }); + + if (authLoading || !isAuthenticated) { + return ( +
+
Loading...
+
+ ); + } + + const filteredProfiles = profiles?.filter((p: any) => + p.username?.toLowerCase().includes(searchQuery.toLowerCase()) || + p.email?.toLowerCase().includes(searchQuery.toLowerCase()) + ) || []; + + const handleLogout = async () => { + await logout(); + setLocation("/"); + }; + + const toggleVerified = (profile: any) => { + updateProfileMutation.mutate({ + id: profile.id, + updates: { is_verified: !profile.is_verified } + }); + }; + + return ( +
+ {/* Sidebar */} +
+
+

+ AeThex +

+

Command Center

+
+ + + +
+
+
+ +
+
+
{user?.username}
+
+ {user?.isAdmin ? "Administrator" : "Member"} +
+
+
+ +
+
+ + {/* Main Content */} +
+
+
+
+

+ Architects +

+

+ {profiles?.length || 0} registered architects +

+
+ + {/* Search */} +
+ + setSearchQuery(e.target.value)} + className="bg-card border border-white/10 pl-10 pr-4 py-2 text-sm text-white placeholder-muted-foreground focus:border-primary/50 focus:outline-none w-64" + data-testid="input-search" + /> +
+
+ + {/* Table */} +
+ + + + + + + + + + + + + + {isLoading ? ( + + + + ) : filteredProfiles.length === 0 ? ( + + + + ) : ( + filteredProfiles.map((profile: any) => ( + + + + + + + + + + )) + )} + +
UserRoleLevelXPStatusVerifiedActions
+ Loading... +
+ No architects found +
+
+ {profile.username} +
+
{profile.username}
+
{profile.email}
+
+
+
+ + {profile.role} + + {profile.level}{profile.total_xp} + + {profile.status} + + + + + +
+
+
+
+ + {/* Profile Detail Modal */} + {selectedProfile && ( +
+ +
+
+ {selectedProfile.username} +
+

{selectedProfile.username}

+

{selectedProfile.email}

+
+
+ +
+ +
+
+ +
{selectedProfile.role}
+
+
+ +
{selectedProfile.level}
+
+
+ +
{selectedProfile.total_xp}
+
+
+ +
+ {selectedProfile.status} +
+
+
+ +
{selectedProfile.aethex_passport_id}
+
+
+ +
+ {selectedProfile.is_verified ? 'Yes' : 'No'} +
+
+
+ +
{selectedProfile.bio || 'No bio'}
+
+
+ +
+ {selectedProfile.skills?.map((skill: string, i: number) => ( + + {skill} + + )) || No skills listed} +
+
+
+ +
+ + +
+
+
+ )} +
+ ); +} + +function NavItem({ icon, label, href, active = false }: { + icon: React.ReactNode; + label: string; + href: string; + active?: boolean; +}) { + return ( + +
+ {icon} + {label} +
+ + ); +} diff --git a/client/src/pages/admin-credentials.tsx b/client/src/pages/admin-credentials.tsx new file mode 100644 index 0000000..563ba37 --- /dev/null +++ b/client/src/pages/admin-credentials.tsx @@ -0,0 +1,167 @@ +import { useEffect } from "react"; +import { Link, useLocation } from "wouter"; +import { useAuth } from "@/lib/auth"; +import { + Users, FileCode, Shield, Activity, LogOut, + BarChart3, User, Award, Clock, AlertTriangle +} from "lucide-react"; + +export default function AdminCredentials() { + const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth(); + const [, setLocation] = useLocation(); + + useEffect(() => { + if (!authLoading && !isAuthenticated) { + setLocation("/login"); + } + }, [authLoading, isAuthenticated, setLocation]); + + if (authLoading || !isAuthenticated) { + return ( +
+
Loading...
+
+ ); + } + + const handleLogout = async () => { + await logout(); + setLocation("/"); + }; + + return ( +
+ {/* Sidebar */} +
+
+

+ AeThex +

+

Command Center

+
+ + + +
+
+
+ +
+
+
{user?.username}
+
+ {user?.isAdmin ? "Administrator" : "Member"} +
+
+
+ +
+
+ + {/* Main Content */} +
+
+
+

+ Credential Issuance +

+

+ Manage Codex certifications and passport credentials +

+
+ + {/* Coming Soon Notice */} +
+
+ +
+

Codex Certification System

+

Under Development

+
+
+

+ The credential issuance system will allow administrators to: +

+
    +
  • +
    + Issue Architect Passports with unique identifiers +
  • +
  • +
    + Award skill certifications based on completed curriculum +
  • +
  • +
    + Verify and validate credentials across platforms +
  • +
  • +
    + Revoke credentials when necessary +
  • +
+
+ + {/* Placeholder Stats */} +
+
+
+ + Issued +
+
0
+
Passports Issued
+
+
+
+ + Pending +
+
0
+
Awaiting Review
+
+
+
+ + Revoked +
+
0
+
Credentials Revoked
+
+
+
+
+
+ ); +} + +function NavItem({ icon, label, href, active = false }: { + icon: React.ReactNode; + label: string; + href: string; + active?: boolean; +}) { + return ( + +
+ {icon} + {label} +
+ + ); +} diff --git a/client/src/pages/admin-projects.tsx b/client/src/pages/admin-projects.tsx new file mode 100644 index 0000000..41bd101 --- /dev/null +++ b/client/src/pages/admin-projects.tsx @@ -0,0 +1,193 @@ +import { useEffect } from "react"; +import { motion } from "framer-motion"; +import { Link, useLocation } from "wouter"; +import { useQuery } from "@tanstack/react-query"; +import { useAuth } from "@/lib/auth"; +import { + Users, FileCode, Shield, Activity, LogOut, + BarChart3, User, ExternalLink +} from "lucide-react"; + +export default function AdminProjects() { + const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth(); + const [, setLocation] = useLocation(); + + useEffect(() => { + if (!authLoading && !isAuthenticated) { + setLocation("/login"); + } + }, [authLoading, isAuthenticated, setLocation]); + + const { data: projects, isLoading } = useQuery({ + queryKey: ["projects"], + queryFn: async () => { + const res = await fetch("/api/projects"); + return res.json(); + }, + enabled: isAuthenticated, + }); + + if (authLoading || !isAuthenticated) { + return ( +
+
Loading...
+
+ ); + } + + const handleLogout = async () => { + await logout(); + setLocation("/"); + }; + + return ( +
+ {/* Sidebar */} +
+
+

+ AeThex +

+

Command Center

+
+ + + +
+
+
+ +
+
+
{user?.username}
+
+ {user?.isAdmin ? "Administrator" : "Member"} +
+
+
+ +
+
+ + {/* Main Content */} +
+
+
+

+ Projects +

+

+ {projects?.length || 0} active projects +

+
+ + {/* Projects Grid */} +
+ {isLoading ? ( +
+ Loading projects... +
+ ) : projects?.length === 0 ? ( +
+ No projects found +
+ ) : ( + projects?.map((project: any) => ( + +
+
+

{project.title}

+
{project.engine}
+
+ + {project.status} + +
+ +

+ {project.description || 'No description'} +

+ + {/* Progress Bar */} +
+
+ Progress + {project.progress}% +
+
+
+
+
+ +
+ + {project.priority} + + + {project.github_url && ( + + GitHub + + )} +
+ + )) + )} +
+
+
+
+ ); +} + +function NavItem({ icon, label, href, active = false }: { + icon: React.ReactNode; + label: string; + href: string; + active?: boolean; +}) { + return ( + +
+ {icon} + {label} +
+ + ); +} diff --git a/client/src/pages/admin.tsx b/client/src/pages/admin.tsx new file mode 100644 index 0000000..9cb7169 --- /dev/null +++ b/client/src/pages/admin.tsx @@ -0,0 +1,284 @@ +import { useEffect } from "react"; +import { motion } from "framer-motion"; +import { Link, useLocation } from "wouter"; +import { useQuery } from "@tanstack/react-query"; +import { useAuth } from "@/lib/auth"; +import { + Users, FileCode, Shield, Activity, LogOut, + Home, BarChart3, Settings, ChevronRight, User +} from "lucide-react"; +import gridBg from '@assets/generated_images/dark_subtle_digital_grid_texture.png'; + +export default function Admin() { + const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth(); + const [, setLocation] = useLocation(); + + useEffect(() => { + if (!authLoading && !isAuthenticated) { + setLocation("/login"); + } + }, [authLoading, isAuthenticated, setLocation]); + + const { data: metrics } = useQuery({ + queryKey: ["metrics"], + queryFn: async () => { + const res = await fetch("/api/metrics"); + return res.json(); + }, + enabled: isAuthenticated, + }); + + const { data: profiles } = useQuery({ + queryKey: ["profiles"], + queryFn: async () => { + const res = await fetch("/api/profiles"); + return res.json(); + }, + enabled: isAuthenticated, + }); + + const { data: projects } = useQuery({ + queryKey: ["projects"], + queryFn: async () => { + const res = await fetch("/api/projects"); + return res.json(); + }, + enabled: isAuthenticated, + }); + + if (authLoading) { + return ( +
+
Loading...
+
+ ); + } + + if (!isAuthenticated) { + return null; + } + + const handleLogout = async () => { + await logout(); + setLocation("/"); + }; + + return ( +
+ {/* Sidebar */} +
+
+

+ AeThex +

+

Command Center

+
+ + + +
+
+
+ +
+
+
{user?.username}
+
+ {user?.isAdmin ? "Administrator" : "Member"} +
+
+
+ +
+
+ + {/* Main Content */} +
+
+ +
+
+

+ Dashboard +

+

+ Real-time ecosystem metrics from Supabase +

+
+ + {/* Metrics Grid */} +
+ } + /> + } + /> + } + /> + } + /> +
+ + {/* Recent Activity */} +
+ {/* Recent Profiles */} +
+

+ + Recent Architects +

+
+ {profiles?.slice(0, 5).map((profile: any) => ( +
+
+ {profile.username} +
+
{profile.username}
+
+ Level {profile.level} • {profile.role} +
+
+
+
+ {profile.status} +
+
+ ))} +
+
+ + {/* Recent Projects */} +
+

+ + Active Projects +

+
+ {projects?.slice(0, 5).map((project: any) => ( +
+
+
{project.title}
+
{project.engine}
+
+
+
+
+
+
{project.progress}%
+
+
+ ))} +
+
+
+ + {/* XP Stats */} +
+

+ Ecosystem Stats +

+
+
+
+ {metrics?.totalXP?.toLocaleString() || 0} +
+
Total XP Earned
+
+
+
+ {metrics?.avgLevel || 1} +
+
Avg Level
+
+
+
+ {profiles?.filter((p: any) => p.onboarded).length || 0} +
+
Onboarded
+
+
+
+ {profiles?.filter((p: any) => p.role === 'admin').length || 0} +
+
Admins
+
+
+
+
+
+
+ ); +} + +function NavItem({ icon, label, href, active = false }: { + icon: React.ReactNode; + label: string; + href: string; + active?: boolean; +}) { + return ( + +
+ {icon} + {label} +
+ + ); +} + +function MetricCard({ title, value, icon }: { title: string; value: number; icon: React.ReactNode }) { + return ( + +
+
{title}
+ {icon} +
+
{value}
+
+ ); +} diff --git a/client/src/pages/dashboard.tsx b/client/src/pages/dashboard.tsx index 887c1fd..9a69561 100644 --- a/client/src/pages/dashboard.tsx +++ b/client/src/pages/dashboard.tsx @@ -1,29 +1,38 @@ import { motion } from "framer-motion"; import { Link } from "wouter"; +import { useQuery } from "@tanstack/react-query"; import { ArrowLeft, Users, ShieldAlert, Globe, Activity, TrendingUp, Target } from "lucide-react"; -import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis, Tooltip, LineChart, Line } from "recharts"; +import { Bar, BarChart, ResponsiveContainer, LineChart, Line, Tooltip } from "recharts"; import mapBg from '@assets/generated_images/abstract_holographic_world_map_data_visualization.png'; -const MOCK_DATA = [ - { name: "Mon", value: 400 }, - { name: "Tue", value: 300 }, - { name: "Wed", value: 550 }, - { name: "Thu", value: 450 }, - { name: "Fri", value: 700 }, - { name: "Sat", value: 600 }, - { name: "Sun", value: 800 }, -]; - -const THREAT_DATA = [ - { name: "00:00", value: 12 }, - { name: "04:00", value: 8 }, - { name: "08:00", value: 45 }, - { name: "12:00", value: 120 }, - { name: "16:00", value: 90 }, - { name: "20:00", value: 35 }, -]; - export default function Dashboard() { + const { data: metrics } = useQuery({ + queryKey: ["metrics"], + queryFn: async () => { + const res = await fetch("/api/metrics"); + return res.json(); + }, + }); + + const MOCK_DATA = [ + { name: "Mon", value: 400 }, + { name: "Tue", value: 300 }, + { name: "Wed", value: 550 }, + { name: "Thu", value: 450 }, + { name: "Fri", value: 700 }, + { name: "Sat", value: 600 }, + { name: "Sun", value: 800 }, + ]; + + const THREAT_DATA = [ + { name: "00:00", value: 12 }, + { name: "04:00", value: 8 }, + { name: "08:00", value: 45 }, + { name: "12:00", value: 120 }, + { name: "16:00", value: 90 }, + { name: "20:00", value: 35 }, + ]; + return (
@@ -47,7 +56,7 @@ export default function Dashboard() { Axiom Command -

Global Ecosystem Status // Real-time Telemetry

+

Global Ecosystem Status // Live Data from Supabase

@@ -60,18 +69,34 @@ export default function Dashboard() {
- {/* KPI Grid */} + {/* KPI Grid - Live Data */}
- } /> - } /> - } /> - } /> + } + /> + } + /> + } + /> + } + />
{/* Charts Section */}
- {/* Map / Main Viz (Placeholder for now, using background) */} + {/* Map / Main Viz */}

@@ -83,14 +108,11 @@ export default function Dashboard() {
-
+
-
+
- - {/* Grid Overlay */} -
@@ -125,12 +147,45 @@ export default function Dashboard() {
+ {/* XP Stats */} +
+

+ Ecosystem Stats (Live) +

+
+
+
+ {metrics?.totalXP?.toLocaleString() || 0} +
+
Total XP Earned
+
+
+
+ {metrics?.avgLevel || 1} +
+
Avg Level
+
+
+
+ {metrics?.totalProfiles || 0} +
+
Registered
+
+
+
+ {metrics?.onlineUsers || 0} +
+
Online Now
+
+
+
+
); } -function Card({ title, value, change, icon }: { title: string, value: string, change: string, icon: React.ReactNode }) { +function Card({ title, value, icon }: { title: string, value: number, icon: React.ReactNode }) { return (
{value}
-
{change}
) diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx index d4534aa..8c8b5b3 100644 --- a/client/src/pages/home.tsx +++ b/client/src/pages/home.tsx @@ -1,9 +1,18 @@ import { motion } from "framer-motion"; import { Link } from "wouter"; -import { Shield, FileCode, Terminal as TerminalIcon, ChevronRight, BarChart3, Network } from "lucide-react"; +import { useQuery } from "@tanstack/react-query"; +import { Shield, FileCode, Terminal as TerminalIcon, ChevronRight, BarChart3, Network, ExternalLink, Lock } from "lucide-react"; import gridBg from '@assets/generated_images/dark_subtle_digital_grid_texture.png'; export default function Home() { + const { data: metrics } = useQuery({ + queryKey: ["metrics"], + queryFn: async () => { + const res = await fetch("/api/metrics"); + return res.json(); + }, + }); + return (
{/* Background Texture */} @@ -12,7 +21,37 @@ export default function Home() { style={{ backgroundImage: `url(${gridBg})`, backgroundSize: 'cover' }} /> -
+ {/* Navigation */} + + +
{/* Header */}
- System Online: v4.2 + The Operating System for the Metaverse

AeThex

- The Operating System for the Metaverse. + We train Architects. We build the Shield. We define the Law.

+ {/* Live Metrics */} + {metrics && ( + +
+
{metrics.totalProfiles}
+
Architects
+
+
+
{metrics.totalProjects}
+
Projects
+
+
+
{metrics.onlineUsers}
+
Online Now
+
+
+
{metrics.totalXP?.toLocaleString()}
+
Total XP
+
+
+ )} + {/* The Trinity Cards */}
- {/* Axiom -> Dashboard */} - + {/* Axiom -> Pitch */} +

Axiom

- The Foundation. View the global command center, active architect metrics, and ecosystem health. + The Law. Our dual-entity protocol creates a self-sustaining ecosystem. The Foundation trains; the Corporation secures.

- Open Dashboard + View Investor Pitch
- {/* Codex -> Curriculum (with Passport link inside) */} - + {/* Codex -> Foundation */} +

Codex

- The Standard. Explore the skill tree, mastery nodes, and view your Architect Credential. + The Standard. Elite training through gamified curriculum. Certifications that employers trust. The Passport to the Metaverse.

- View Tech Tree + Enter Foundation
- +
- {/* Aegis -> Terminal */} - + {/* Aegis -> Studio */} +

Aegis

- The Shield. Enter the secure build environment. New: Live Threat Simulation available. + The Shield. Real-time security for the build environment. PII scrubbing. Threat intervention. Protection for every line of code.

- Launch Terminal + Launch Studio
- +
+ {/* Demo Links */} + + + + + + + + + + + + {/* Footer */} - AeThex Foundry © 2025 // Authorized Personnel Only + AeThex Foundry © 2025 // Building the Future of the Metaverse
diff --git a/client/src/pages/login.tsx b/client/src/pages/login.tsx new file mode 100644 index 0000000..0430672 --- /dev/null +++ b/client/src/pages/login.tsx @@ -0,0 +1,118 @@ +import { useState } from "react"; +import { motion } from "framer-motion"; +import { useLocation } from "wouter"; +import { Shield, Lock, AlertCircle } from "lucide-react"; +import { useAuth } from "@/lib/auth"; +import gridBg from '@assets/generated_images/dark_subtle_digital_grid_texture.png'; + +export default function Login() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const { login } = useAuth(); + const [, setLocation] = useLocation(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); + setIsLoading(true); + + try { + await login(username, password); + setLocation("/admin"); + } catch (err: any) { + setError(err.message || "Login failed"); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+ + +
+
+ +
+

+ AeThex Command +

+

+ Authorized Personnel Only +

+
+ +
+ {error && ( +
+ + {error} +
+ )} + +
+ + setUsername(e.target.value)} + className="w-full bg-card border border-white/10 px-4 py-3 text-white placeholder-muted-foreground focus:border-primary/50 focus:outline-none transition-colors" + placeholder="Enter username" + data-testid="input-username" + required + /> +
+ +
+ + setPassword(e.target.value)} + className="w-full bg-card border border-white/10 px-4 py-3 text-white placeholder-muted-foreground focus:border-primary/50 focus:outline-none transition-colors" + placeholder="Enter password" + data-testid="input-password" + required + /> +
+ + +
+ + +
+
+ ); +} diff --git a/client/src/pages/pitch.tsx b/client/src/pages/pitch.tsx new file mode 100644 index 0000000..bb69aa7 --- /dev/null +++ b/client/src/pages/pitch.tsx @@ -0,0 +1,208 @@ +import { motion } from "framer-motion"; +import { Link } from "wouter"; +import { useQuery } from "@tanstack/react-query"; +import { ArrowLeft, Shield, FileCode, Terminal, ChevronRight, Building, GraduationCap, Users, Activity } from "lucide-react"; +import gridBg from '@assets/generated_images/dark_subtle_digital_grid_texture.png'; + +export default function Pitch() { + const { data: metrics } = useQuery({ + queryKey: ["metrics"], + queryFn: async () => { + const res = await fetch("/api/metrics"); + return res.json(); + }, + }); + + return ( +
+
+ +
+ + + + + + {/* Title */} + +
+ The Axiom Protocol +
+

+ Investor Brief +

+

+ AeThex is a self-sustaining ecosystem built on a dual-entity model that transforms raw talent into certified Metaverse Architects. +

+
+ + {/* The Dual Entity Model */} + + {/* Foundation */} +
+
+
+
+ +
+
+
Non-Profit
+

The Foundation

+
+
+

+ Uses The Codex to train elite Architects. + Gamified curriculum, verified certifications, and a clear path from beginner to master. +

+
+
+ + Codex Standard Certification +
+
+ + {metrics?.totalProfiles || 0} Active Architects +
+
+
+ + {/* Corporation */} +
+
+
+
+ +
+
+
For-Profit
+

The Corporation

+
+
+

+ Builds The Aegis to secure the Metaverse. + Enterprise security layer, PII scrubbing, threat intervention—sold to platforms and publishers. +

+
+
+ + Real-time Security Layer +
+
+ + {metrics?.totalProjects || 0} Protected Projects +
+
+
+ + + {/* The Holy Trinity */} + +

+ The Holy Trinity +

+ +
+
+ +

Axiom

+

The Law. The foundational protocol governing the ecosystem.

+
+
+ +

Codex

+

The Standard. Certification that proves mastery.

+
+
+ +

Aegis

+

The Shield. Real-time protection for builders.

+
+
+
+ + {/* The Pitch Quote */} + +
+ "We take raw talent, train them on our laws, and arm them with our weapons. + The Foundation creates the workforce; The Corporation sells the security." +
+
+ + {/* Live Metrics */} + {metrics && ( + +

+ Live Ecosystem Metrics +

+
+
+
{metrics.totalProfiles}
+
Architects
+
+
+
{metrics.totalProjects}
+
Projects
+
+
+
{metrics.totalXP?.toLocaleString()}
+
Total XP
+
+
+
{metrics.onlineUsers}
+
Online Now
+
+
+
+ )} + + {/* CTA */} + +

+ Ready to learn more about investment opportunities? +

+ + Contact Our Team + +
+ +
+
+ ); +} diff --git a/package-lock.json b/package-lock.json index 5476984..228629e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,8 @@ "@radix-ui/react-tooltip": "^1.2.8", "@supabase/supabase-js": "^2.87.3", "@tanstack/react-query": "^5.60.5", + "@types/bcrypt": "^6.0.0", + "bcrypt": "^6.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -4449,6 +4451,15 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -4828,6 +4839,20 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -6545,12 +6570,20 @@ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, - "node_modules/node-gyp-build": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.3.tgz", - "integrity": "sha512-EMS95CMJzdoSKoIiXo8pxKoL8DYxwIZXYlLmgPb8KUv794abpnLK6ynsCAWNliOjREKruYKdzbh76HHYUHX7nw==", + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", - "optional": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", diff --git a/package.json b/package.json index bee0729..b32633f 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,8 @@ "@radix-ui/react-tooltip": "^1.2.8", "@supabase/supabase-js": "^2.87.3", "@tanstack/react-query": "^5.60.5", + "@types/bcrypt": "^6.0.0", + "bcrypt": "^6.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/server/index.ts b/server/index.ts index f4219e2..dd21822 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,4 +1,5 @@ import express, { type Request, Response, NextFunction } from "express"; +import session from "express-session"; import { registerRoutes } from "./routes"; import { serveStatic } from "./static"; import { createServer } from "http"; @@ -12,6 +13,27 @@ declare module "http" { } } +// Require session secret in production +const sessionSecret = process.env.SESSION_SECRET; +if (process.env.NODE_ENV === "production" && !sessionSecret) { + throw new Error("SESSION_SECRET environment variable is required in production"); +} + +// Session configuration with security best practices +app.use( + session({ + secret: sessionSecret || "dev-only-secret-not-for-prod", + resave: false, + saveUninitialized: false, + cookie: { + secure: process.env.NODE_ENV === "production", + httpOnly: true, + sameSite: "strict", // CSRF protection + maxAge: 24 * 60 * 60 * 1000, // 24 hours + }, + }) +); + app.use( express.json({ verify: (req, _res, buf) => { diff --git a/server/routes.ts b/server/routes.ts index 5839775..a315b2b 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1,90 +1,186 @@ -import type { Express } from "express"; +import type { Express, Request, Response, NextFunction } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; -import { supabase } from "./supabase"; +import { loginSchema } from "@shared/schema"; +import bcrypt from "bcrypt"; + +// Extend session type +declare module 'express-session' { + interface SessionData { + userId?: string; + isAdmin?: boolean; + } +} + +// Auth middleware - requires any authenticated user +function requireAuth(req: Request, res: Response, next: NextFunction) { + if (!req.session.userId) { + return res.status(401).json({ error: "Unauthorized" }); + } + next(); +} + +// Admin middleware - requires authenticated admin user +function requireAdmin(req: Request, res: Response, next: NextFunction) { + if (!req.session.userId) { + return res.status(401).json({ error: "Unauthorized" }); + } + if (!req.session.isAdmin) { + return res.status(403).json({ error: "Admin access required" }); + } + next(); +} export async function registerRoutes( httpServer: Server, app: Express ): Promise { - // API: Explore database schema (temporary - for development) - app.get("/api/schema", async (req, res) => { + // ========== AUTH ROUTES ========== + + // Login + app.post("/api/auth/login", async (req, res) => { try { - // Query information_schema to get all tables - const { data, error } = await supabase - .from('information_schema.tables') - .select('table_name') - .eq('table_schema', 'public'); - - if (error) { - // Alternative: try querying a known table or use RPC - // Let's try to get tables via a different approach - const tablesQuery = await supabase.rpc('get_tables'); - if (tablesQuery.error) { - return res.json({ - error: error.message, - hint: "Could not query schema. Tables may need to be accessed directly.", - supabaseConnected: true - }); - } - return res.json({ tables: tablesQuery.data }); + const result = loginSchema.safeParse(req.body); + if (!result.success) { + return res.status(400).json({ error: "Invalid credentials" }); } - res.json({ tables: data }); + const { username, password } = result.data; + const user = await storage.getUserByUsername(username); + + if (!user) { + return res.status(401).json({ error: "Invalid credentials" }); + } + + const isValid = await bcrypt.compare(password, user.password); + if (!isValid) { + return res.status(401).json({ error: "Invalid credentials" }); + } + + // Regenerate session on login to prevent session fixation + req.session.regenerate((err) => { + if (err) { + return res.status(500).json({ error: "Session error" }); + } + + req.session.userId = user.id; + req.session.isAdmin = user.is_admin ?? false; + + res.json({ + success: true, + user: { + id: user.id, + username: user.username, + isAdmin: user.is_admin + } + }); + }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); - - // API: Test specific tables that might exist - app.get("/api/explore", async (req, res) => { - const potentialTables = [ - 'users', 'architects', 'profiles', 'credentials', 'certificates', - 'skills', 'curriculum', 'courses', 'modules', 'lessons', - 'threats', 'events', 'logs', 'projects', 'teams', - 'organizations', 'members', 'enrollments', 'progress' - ]; - - const results: Record = {}; - - for (const table of potentialTables) { - try { - const { data, error, count } = await supabase - .from(table) - .select('*', { count: 'exact', head: true }); - - if (!error) { - results[table] = { exists: true, count }; - } - } catch (e) { - // Table doesn't exist, skip + + // Logout + app.post("/api/auth/logout", (req, res) => { + req.session.destroy((err) => { + if (err) { + return res.status(500).json({ error: "Logout failed" }); } + res.clearCookie('connect.sid'); + res.json({ success: true }); + }); + }); + + // Get current session + app.get("/api/auth/session", async (req, res) => { + if (!req.session.userId) { + return res.json({ authenticated: false }); + } + + const user = await storage.getUser(req.session.userId); + if (!user) { + return res.json({ authenticated: false }); } res.json({ - foundTables: Object.keys(results), - details: results, - supabaseUrl: process.env.SUPABASE_URL ? 'configured' : 'missing' + authenticated: true, + user: { + id: user.id, + username: user.username, + isAdmin: user.is_admin + } }); }); - - // API: Get sample data from a specific table - app.get("/api/table/:name", async (req, res) => { - const { name } = req.params; - const limit = parseInt(req.query.limit as string) || 10; - + + // ========== PUBLIC API ROUTES ========== + + // Get ecosystem metrics (public - for dashboard) + app.get("/api/metrics", async (req, res) => { try { - const { data, error, count } = await supabase - .from(name) - .select('*', { count: 'exact' }) - .limit(limit); - - if (error) { - return res.status(400).json({ error: error.message }); + const metrics = await storage.getMetrics(); + res.json(metrics); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); + + // ========== ADMIN-PROTECTED API ROUTES ========== + + // Get all profiles (admin only) + app.get("/api/profiles", requireAdmin, async (req, res) => { + try { + const profiles = await storage.getProfiles(); + res.json(profiles); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); + + // Get single profile (admin only) + app.get("/api/profiles/:id", requireAdmin, async (req, res) => { + try { + const profile = await storage.getProfile(req.params.id); + if (!profile) { + return res.status(404).json({ error: "Profile not found" }); } - - res.json({ table: name, count, sample: data }); + res.json(profile); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); + + // Update profile (admin only) + app.patch("/api/profiles/:id", requireAdmin, async (req, res) => { + try { + const profile = await storage.updateProfile(req.params.id, req.body); + if (!profile) { + return res.status(404).json({ error: "Profile not found" }); + } + res.json(profile); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); + + // Get all projects (admin only) + app.get("/api/projects", requireAdmin, async (req, res) => { + try { + const projects = await storage.getProjects(); + res.json(projects); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); + + // Get single project (admin only) + app.get("/api/projects/:id", requireAdmin, async (req, res) => { + try { + const project = await storage.getProject(req.params.id); + if (!project) { + return res.status(404).json({ error: "Project not found" }); + } + res.json(project); } catch (err: any) { res.status(500).json({ error: err.message }); } diff --git a/server/storage.ts b/server/storage.ts index ee25bd1..2c5af7e 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -1,38 +1,149 @@ -import { type User, type InsertUser } from "@shared/schema"; -import { randomUUID } from "crypto"; - -// modify the interface with any CRUD methods -// you might need +import { type User, type Profile, type Project } from "@shared/schema"; +import { supabase } from "./supabase"; export interface IStorage { + // Users getUser(id: string): Promise; getUserByUsername(username: string): Promise; - createUser(user: InsertUser): Promise; + + // Profiles + getProfiles(): Promise; + getProfile(id: string): Promise; + getProfileByUsername(username: string): Promise; + updateProfile(id: string, data: Partial): Promise; + + // Projects + getProjects(): Promise; + getProject(id: string): Promise; + + // Metrics + getMetrics(): Promise<{ + totalProfiles: number; + totalProjects: number; + onlineUsers: number; + verifiedUsers: number; + totalXP: number; + avgLevel: number; + }>; } -export class MemStorage implements IStorage { - private users: Map; - - constructor() { - this.users = new Map(); - } - +export class SupabaseStorage implements IStorage { + async getUser(id: string): Promise { - return this.users.get(id); + const { data, error } = await supabase + .from('users') + .select('*') + .eq('id', id) + .single(); + + if (error || !data) return undefined; + return data as User; } - + async getUserByUsername(username: string): Promise { - return Array.from(this.users.values()).find( - (user) => user.username === username, - ); + const { data, error } = await supabase + .from('users') + .select('*') + .eq('username', username) + .single(); + + if (error || !data) return undefined; + return data as User; } - - async createUser(insertUser: InsertUser): Promise { - const id = randomUUID(); - const user: User = { ...insertUser, id }; - this.users.set(id, user); - return user; + + async getProfiles(): Promise { + const { data, error } = await supabase + .from('profiles') + .select('*') + .order('created_at', { ascending: false }); + + if (error) return []; + return data as Profile[]; + } + + async getProfile(id: string): Promise { + const { data, error } = await supabase + .from('profiles') + .select('*') + .eq('id', id) + .single(); + + if (error || !data) return undefined; + return data as Profile; + } + + async getProfileByUsername(username: string): Promise { + const { data, error } = await supabase + .from('profiles') + .select('*') + .eq('username', username) + .single(); + + if (error || !data) return undefined; + return data as Profile; + } + + async updateProfile(id: string, updates: Partial): Promise { + const { data, error } = await supabase + .from('profiles') + .update({ ...updates, updated_at: new Date().toISOString() }) + .eq('id', id) + .select() + .single(); + + if (error || !data) return undefined; + return data as Profile; + } + + async getProjects(): Promise { + const { data, error } = await supabase + .from('projects') + .select('*') + .order('created_at', { ascending: false }); + + if (error) return []; + return data as Project[]; + } + + async getProject(id: string): Promise { + const { data, error } = await supabase + .from('projects') + .select('*') + .eq('id', id) + .single(); + + if (error || !data) return undefined; + return data as Project; + } + + async getMetrics(): Promise<{ + totalProfiles: number; + totalProjects: number; + onlineUsers: number; + verifiedUsers: number; + totalXP: number; + avgLevel: number; + }> { + // Get profiles for metrics + const profiles = await this.getProfiles(); + const projects = await this.getProjects(); + + const onlineUsers = profiles.filter(p => p.status === 'online').length; + const verifiedUsers = profiles.filter(p => p.is_verified).length; + const totalXP = profiles.reduce((sum, p) => sum + (p.total_xp || 0), 0); + const avgLevel = profiles.length > 0 + ? profiles.reduce((sum, p) => sum + (p.level || 1), 0) / profiles.length + : 1; + + return { + totalProfiles: profiles.length, + totalProjects: projects.length, + onlineUsers, + verifiedUsers, + totalXP, + avgLevel: Math.round(avgLevel * 10) / 10, + }; } } -export const storage = new MemStorage(); +export const storage = new SupabaseStorage(); diff --git a/shared/schema.ts b/shared/schema.ts index 8e71891..99d0bb5 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -1,12 +1,15 @@ -import { sql } from "drizzle-orm"; -import { pgTable, text, varchar } from "drizzle-orm/pg-core"; +import { pgTable, text, varchar, boolean, integer, timestamp, json } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; import { z } from "zod"; +// Users table (auth) export const users = pgTable("users", { - id: varchar("id").primaryKey().default(sql`gen_random_uuid()`), + id: varchar("id").primaryKey(), username: text("username").notNull().unique(), password: text("password").notNull(), + is_active: boolean("is_active").default(true), + is_admin: boolean("is_admin").default(false), + created_at: timestamp("created_at").defaultNow(), }); export const insertUserSchema = createInsertSchema(users).pick({ @@ -16,3 +19,73 @@ export const insertUserSchema = createInsertSchema(users).pick({ export type InsertUser = z.infer; export type User = typeof users.$inferSelect; + +// Profiles table (rich user data) +export const profiles = pgTable("profiles", { + id: varchar("id").primaryKey(), + username: text("username"), + role: text("role").default("member"), + onboarded: boolean("onboarded").default(false), + bio: text("bio"), + skills: json("skills").$type(), + avatar_url: text("avatar_url"), + banner_url: text("banner_url"), + social_links: json("social_links").$type>(), + loyalty_points: integer("loyalty_points").default(0), + email: text("email"), + created_at: timestamp("created_at").defaultNow(), + updated_at: timestamp("updated_at").defaultNow(), + user_type: text("user_type").default("community_member"), + experience_level: text("experience_level").default("beginner"), + full_name: text("full_name"), + location: text("location"), + total_xp: integer("total_xp").default(0), + level: integer("level").default(1), + aethex_passport_id: varchar("aethex_passport_id"), + status: text("status").default("offline"), + is_verified: boolean("is_verified").default(false), +}); + +export const insertProfileSchema = createInsertSchema(profiles).omit({ + id: true, + created_at: true, + updated_at: true, +}); + +export type InsertProfile = z.infer; +export type Profile = typeof profiles.$inferSelect; + +// Projects table +export const projects = pgTable("projects", { + id: varchar("id").primaryKey(), + owner_id: varchar("owner_id"), + title: text("title").notNull(), + description: text("description"), + status: text("status").default("planning"), + github_url: text("github_url"), + created_at: timestamp("created_at").defaultNow(), + updated_at: timestamp("updated_at").defaultNow(), + user_id: varchar("user_id"), + engine: text("engine"), + priority: text("priority").default("medium"), + progress: integer("progress").default(0), + live_url: text("live_url"), + technologies: json("technologies").$type(), +}); + +export const insertProjectSchema = createInsertSchema(projects).omit({ + id: true, + created_at: true, + updated_at: true, +}); + +export type InsertProject = z.infer; +export type Project = typeof projects.$inferSelect; + +// Login schema for validation +export const loginSchema = z.object({ + username: z.string().min(1, "Username is required"), + password: z.string().min(1, "Password is required"), +}); + +export type LoginInput = z.infer;