new file: server/api/os.ts

This commit is contained in:
MrPiglr 2025-12-24 01:25:27 +00:00
parent e1c3b9d745
commit 929d293e5f
18 changed files with 1387 additions and 26 deletions

View file

@ -31,14 +31,15 @@ import AeThexOS from "@/pages/os";
import Network from "@/pages/network"; import Network from "@/pages/network";
import NetworkProfile from "@/pages/network-profile"; import NetworkProfile from "@/pages/network-profile";
import Lab from "@/pages/lab"; import Lab from "@/pages/lab";
import Projects from "@/pages/projects"; import HubProjects from "@/pages/hub/projects";
import Messaging from "@/pages/messaging"; import HubMessaging from "@/pages/hub/messaging";
import Marketplace from "@/pages/marketplace"; import HubMarketplace from "@/pages/hub/marketplace";
import Settings from "@/pages/settings"; import HubSettings from "@/pages/hub/settings";
import FileManager from "@/pages/file-manager"; import HubFileManager from "@/pages/hub/file-manager";
import CodeGallery from "@/pages/code-gallery"; import HubCodeGallery from "@/pages/hub/code-gallery";
import Notifications from "@/pages/notifications"; import HubNotifications from "@/pages/hub/notifications";
import Analytics from "@/pages/analytics"; import HubAnalytics from "@/pages/hub/analytics";
import OsLink from "@/pages/os/link";
import { LabTerminalProvider } from "@/hooks/use-lab-terminal"; import { LabTerminalProvider } from "@/hooks/use-lab-terminal";
function Router() { function Router() {
@ -67,17 +68,18 @@ function Router() {
<Route path="/admin/notifications">{() => <ProtectedRoute><AdminNotifications /></ProtectedRoute>}</Route> <Route path="/admin/notifications">{() => <ProtectedRoute><AdminNotifications /></ProtectedRoute>}</Route>
<Route path="/pitch" component={Pitch} /> <Route path="/pitch" component={Pitch} />
<Route path="/os" component={AeThexOS} /> <Route path="/os" component={AeThexOS} />
<Route path="/os/link">{() => <ProtectedRoute><OsLink /></ProtectedRoute>}</Route>
<Route path="/network" component={Network} /> <Route path="/network" component={Network} />
<Route path="/network/:slug" component={NetworkProfile} /> <Route path="/network/:slug" component={NetworkProfile} />
<Route path="/lab" component={Lab} /> <Route path="/lab" component={Lab} />
<Route path="/projects">{() => <ProtectedRoute><Projects /></ProtectedRoute>}</Route> <Route path="/hub/projects">{() => <ProtectedRoute><HubProjects /></ProtectedRoute>}</Route>
<Route path="/messaging">{() => <ProtectedRoute><Messaging /></ProtectedRoute>}</Route> <Route path="/hub/messaging">{() => <ProtectedRoute><HubMessaging /></ProtectedRoute>}</Route>
<Route path="/marketplace">{() => <ProtectedRoute><Marketplace /></ProtectedRoute>}</Route> <Route path="/hub/marketplace">{() => <ProtectedRoute><HubMarketplace /></ProtectedRoute>}</Route>
<Route path="/settings">{() => <ProtectedRoute><Settings /></ProtectedRoute>}</Route> <Route path="/hub/settings">{() => <ProtectedRoute><HubSettings /></ProtectedRoute>}</Route>
<Route path="/file-manager">{() => <ProtectedRoute><FileManager /></ProtectedRoute>}</Route> <Route path="/hub/file-manager">{() => <ProtectedRoute><HubFileManager /></ProtectedRoute>}</Route>
<Route path="/code-gallery">{() => <ProtectedRoute><CodeGallery /></ProtectedRoute>}</Route> <Route path="/hub/code-gallery">{() => <ProtectedRoute><HubCodeGallery /></ProtectedRoute>}</Route>
<Route path="/notifications">{() => <ProtectedRoute><Notifications /></ProtectedRoute>}</Route> <Route path="/hub/notifications">{() => <ProtectedRoute><HubNotifications /></ProtectedRoute>}</Route>
<Route path="/analytics">{() => <ProtectedRoute><Analytics /></ProtectedRoute>}</Route> <Route path="/hub/analytics">{() => <ProtectedRoute><HubAnalytics /></ProtectedRoute>}</Route>
<Route component={NotFound} /> <Route component={NotFound} />
</Switch> </Switch>
); );

View file

@ -0,0 +1,216 @@
import { useState, useEffect } from "react";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Github, Globe, MessageSquare, Loader2 } from "lucide-react";
import { useAuth } from "@/lib/auth";
export default function OsLink() {
const { user } = useAuth();
const [linkedIdentities, setLinkedIdentities] = useState<
Array<{ provider: string; external_id: string; verified_at: string }>
>([]);
const [loading, setLoading] = useState(false);
const providers = [
{ name: "Roblox", id: "roblox", icon: Globe, color: "text-red-500" },
{ name: "Discord", id: "discord", icon: MessageSquare, color: "text-indigo-500" },
{ name: "GitHub", id: "github", icon: Github, color: "text-gray-300" },
];
const handleLinkStart = async (provider: string) => {
if (!user?.id) {
alert("Please log in first");
return;
}
setLoading(true);
try {
const res = await fetch("/api/os/link/start", {
method: "POST",
headers: { "Content-Type": "application/json", "x-user-id": user.id },
body: JSON.stringify({ provider }),
});
const { redirect_url } = await res.json();
// In production, redirect to OAuth flow
alert(`Would redirect to: ${redirect_url}`);
} catch (error) {
console.error("Link failed:", error);
alert("Failed to start linking");
} finally {
setLoading(false);
}
};
const handleUnlink = async (provider: string) => {
if (!user?.id) return;
try {
await fetch("/api/os/link/unlink", {
method: "POST",
headers: { "Content-Type": "application/json", "x-user-id": user.id },
body: JSON.stringify({ provider }),
});
setLinkedIdentities(linkedIdentities.filter((id) => id.provider !== provider));
} catch (error) {
console.error("Unlink failed:", error);
alert("Failed to unlink identity");
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 p-8">
<div className="max-w-3xl mx-auto">
{/* Header */}
<div className="mb-12">
<h1 className="text-4xl font-bold text-white mb-2">Identity Linking</h1>
<p className="text-gray-300 text-lg">
Link your accounts to get verified credentials across the AeThex ecosystem.
</p>
<p className="text-gray-400 text-sm mt-4">
💡 <strong>What this means:</strong> Your proofs are portable. Link once, use everywhere.
</p>
</div>
{/* Providers */}
<div className="space-y-3 mb-12">
<h2 className="text-xl font-semibold text-white mb-4">Available Platforms</h2>
{providers.map((provider) => {
const Icon = provider.icon;
const isLinked = linkedIdentities.some((id) => id.provider === provider.id);
return (
<Card key={provider.id} className="bg-slate-800/50 border-slate-700 hover:border-slate-600 transition">
<CardContent className="flex items-center justify-between p-5">
<div className="flex items-center gap-4">
<Icon className={`w-7 h-7 ${provider.color}`} />
<div>
<p className="font-semibold text-white">{provider.name}</p>
{isLinked && (
<p className="text-sm text-green-400">
Linked and verified
</p>
)}
</div>
</div>
<Button
onClick={() =>
isLinked ? handleUnlink(provider.id) : handleLinkStart(provider.id)
}
disabled={loading}
variant={isLinked ? "outline" : "default"}
className={isLinked ? "border-red-500 text-red-500 hover:bg-red-500/10" : ""}
>
{loading ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Linking...
</>
) : isLinked ? (
"Unlink"
) : (
"Link"
)}
</Button>
</CardContent>
</Card>
);
})}
</div>
{/* Info Cards */}
<div className="grid md:grid-cols-2 gap-4">
<Card className="bg-cyan-900/20 border-cyan-700/50">
<CardHeader>
<CardTitle className="text-cyan-400 text-lg flex items-center gap-2">
<span>🔐</span> Your Privacy
</CardTitle>
</CardHeader>
<CardContent className="text-sm text-gray-300">
We never share your linked identities without your consent. Each platform only sees what you allow.
</CardContent>
</Card>
<Card className="bg-green-900/20 border-green-700/50">
<CardHeader>
<CardTitle className="text-green-400 text-lg flex items-center gap-2">
<span></span> Verified Proofs
</CardTitle>
</CardHeader>
<CardContent className="text-sm text-gray-300">
When you link, we create cryptographically signed proofs of your achievements that you can share.
</CardContent>
</Card>
<Card className="bg-purple-900/20 border-purple-700/50">
<CardHeader>
<CardTitle className="text-purple-400 text-lg flex items-center gap-2">
<span>🔗</span> Portable
</CardTitle>
</CardHeader>
<CardContent className="text-sm text-gray-300">
Use your verified credentials across any platform that trusts AeThex, without creating new accounts.
</CardContent>
</Card>
<Card className="bg-blue-900/20 border-blue-700/50">
<CardHeader>
<CardTitle className="text-blue-400 text-lg flex items-center gap-2">
<span>🚪</span> Exit Path
</CardTitle>
</CardHeader>
<CardContent className="text-sm text-gray-300">
If AeThex disappears, your proofs remain valid and your linked accounts are still yours.
</CardContent>
</Card>
</div>
{/* How It Works */}
<Card className="mt-8 bg-slate-800/50 border-slate-700">
<CardHeader>
<CardTitle className="text-white">How It Works</CardTitle>
</CardHeader>
<CardContent className="space-y-4 text-sm text-gray-300">
<div className="space-y-3">
<div className="flex gap-3">
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-cyan-500 text-white flex items-center justify-center text-xs font-bold">1</div>
<div>
<p className="font-semibold text-white">Link Your Account</p>
<p className="text-gray-400">Connect your Roblox, Discord, or GitHub account securely.</p>
</div>
</div>
<div className="flex gap-3">
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-cyan-500 text-white flex items-center justify-center text-xs font-bold">2</div>
<div>
<p className="font-semibold text-white">Verify Ownership</p>
<p className="text-gray-400">We confirm you own the account (OAuth, challenge, etc).</p>
</div>
</div>
<div className="flex gap-3">
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-cyan-500 text-white flex items-center justify-center text-xs font-bold">3</div>
<div>
<p className="font-semibold text-white">Get Verified Proofs</p>
<p className="text-gray-400">Your achievements are signed and portable across platforms.</p>
</div>
</div>
<div className="flex gap-3">
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-cyan-500 text-white flex items-center justify-center text-xs font-bold">4</div>
<div>
<p className="font-semibold text-white">Use Everywhere</p>
<p className="text-gray-400">Share your proofs with any platform that trusts AeThex OS.</p>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Footer */}
<div className="mt-12 text-center text-sm text-gray-400">
<p>
💡 <strong>OS attests; platforms decide.</strong>
</p>
<p className="mt-2">We verify your credentials. Other platforms decide what access to grant.</p>
</div>
</div>
</div>
);
}

View file

@ -148,20 +148,19 @@ CREATE TABLE IF NOT EXISTS "projects" (
-- Create indexes for better query performance -- Create indexes for better query performance
CREATE INDEX IF NOT EXISTS "messages_sender_id_idx" ON "messages" ("sender_id"); CREATE INDEX IF NOT EXISTS "messages_sender_id_idx" ON "messages" ("sender_id");
--> statement-breakpoint --> statement-breakpoint
CREATE INDEX IF NOT EXISTS "messages_recipient_id_idx" ON "messages" ("recipient_id"); -- Removed: marketplace_listings_seller_id_idx (schema mismatch)
--> statement-breakpoint --> statement-breakpoint
CREATE INDEX IF NOT EXISTS "marketplace_listings_seller_id_idx" ON "marketplace_listings" ("seller_id"); -- Removed: marketplace_listings_category_idx (schema mismatch)
--> statement-breakpoint --> statement-breakpoint
CREATE INDEX IF NOT EXISTS "marketplace_listings_category_idx" ON "marketplace_listings" ("category"); -- Removed: files_user_id_idx (schema mismatch)
--> statement-breakpoint --> statement-breakpoint
CREATE INDEX IF NOT EXISTS "files_user_id_idx" ON "files" ("user_id"); -- Removed: files_parent_id_idx (schema mismatch)
--> statement-breakpoint --> statement-breakpoint
CREATE INDEX IF NOT EXISTS "files_parent_id_idx" ON "files" ("parent_id"); -- Removed: notifications_user_id_idx (schema mismatch)
--> statement-breakpoint --> statement-breakpoint
CREATE INDEX IF NOT EXISTS "notifications_user_id_idx" ON "notifications" ("user_id"); -- Removed: user_analytics_user_id_idx (schema mismatch)
--> statement-breakpoint --> statement-breakpoint
CREATE INDEX IF NOT EXISTS "user_analytics_user_id_idx" ON "user_analytics" ("user_id"); -- Removed: code_gallery_creator_id_idx (schema mismatch)
--> statement-breakpoint --> statement-breakpoint
CREATE INDEX IF NOT EXISTS "code_gallery_creator_id_idx" ON "code_gallery" ("creator_id"); -- Removed: projects_user_id_idx (schema mismatch)
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "projects_user_id_idx" ON "projects" ("user_id");

View file

@ -0,0 +1,111 @@
-- AeThex OS Kernel Schema
-- Portable proof system for the entire ecosystem
-- This is the spine: identity coordination + entitlements + verification
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "aethex_subjects" (
"id" varchar PRIMARY KEY NOT NULL DEFAULT gen_random_uuid()::text,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "aethex_subject_identities" (
"id" varchar PRIMARY KEY NOT NULL DEFAULT gen_random_uuid()::text,
"subject_id" varchar NOT NULL REFERENCES "aethex_subjects"("id") ON DELETE CASCADE,
"provider" varchar NOT NULL,
"external_id" varchar NOT NULL,
"external_username" varchar,
"verified_at" timestamp,
"revoked_at" timestamp,
"created_at" timestamp DEFAULT now(),
CONSTRAINT "aethex_subject_identities_provider_external_id_unique" UNIQUE("provider", "external_id")
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "aethex_issuers" (
"id" varchar PRIMARY KEY NOT NULL DEFAULT gen_random_uuid()::text,
"name" varchar NOT NULL,
"issuer_class" varchar NOT NULL,
"scopes" json DEFAULT '[]'::json,
"public_key" text NOT NULL,
"is_active" boolean DEFAULT true,
"metadata" json DEFAULT '{}'::json,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "aethex_issuer_keys" (
"id" varchar PRIMARY KEY NOT NULL DEFAULT gen_random_uuid()::text,
"issuer_id" varchar NOT NULL REFERENCES "aethex_issuers"("id") ON DELETE CASCADE,
"public_key" text NOT NULL,
"private_key_hash" text,
"is_active" boolean DEFAULT true,
"rotated_at" timestamp,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "aethex_entitlements" (
"id" varchar PRIMARY KEY NOT NULL DEFAULT gen_random_uuid()::text,
"issuer_id" varchar NOT NULL REFERENCES "aethex_issuers"("id") ON DELETE CASCADE,
"subject_id" varchar REFERENCES "aethex_subjects"("id") ON DELETE CASCADE,
"external_subject_ref" varchar,
"schema_version" varchar DEFAULT 'v0.1',
"scope" varchar NOT NULL,
"entitlement_type" varchar NOT NULL,
"data" json NOT NULL,
"status" varchar DEFAULT 'active',
"signature" text,
"evidence_hash" varchar,
"issued_by_subject_id" varchar,
"expires_at" timestamp,
"revoked_at" timestamp,
"revocation_reason" text,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "aethex_entitlement_events" (
"id" varchar PRIMARY KEY NOT NULL DEFAULT gen_random_uuid()::text,
"entitlement_id" varchar NOT NULL REFERENCES "aethex_entitlements"("id") ON DELETE CASCADE,
"event_type" varchar NOT NULL,
"actor_id" varchar,
"actor_type" varchar NOT NULL,
"reason" text,
"metadata" json DEFAULT '{}'::json,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "aethex_audit_log" (
"id" varchar PRIMARY KEY NOT NULL DEFAULT gen_random_uuid()::text,
"action" varchar NOT NULL,
"actor_id" varchar,
"actor_type" varchar NOT NULL,
"resource_type" varchar NOT NULL,
"resource_id" varchar NOT NULL,
"changes" json DEFAULT '{}'::json,
"ip_address" varchar,
"user_agent" text,
"status" varchar DEFAULT 'success',
"error_message" text,
"created_at" timestamp DEFAULT now()
);
--> statement-breakpoint
-- OS Indexes for performance
CREATE INDEX IF NOT EXISTS "aethex_subject_identities_subject_id_idx" ON "aethex_subject_identities" ("subject_id");
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "aethex_subject_identities_provider_external_id_idx" ON "aethex_subject_identities" ("provider", "external_id");
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "aethex_issuer_keys_issuer_id_idx" ON "aethex_issuer_keys" ("issuer_id");
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "aethex_entitlements_issuer_id_idx" ON "aethex_entitlements" ("issuer_id");
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "aethex_entitlements_subject_id_idx" ON "aethex_entitlements" ("subject_id");
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "aethex_entitlements_external_subject_ref_idx" ON "aethex_entitlements" ("external_subject_ref");
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "aethex_entitlements_status_idx" ON "aethex_entitlements" ("status");
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "aethex_entitlement_events_entitlement_id_idx" ON "aethex_entitlement_events" ("entitlement_id");
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "aethex_audit_log_action_idx" ON "aethex_audit_log" ("action");
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "aethex_audit_log_resource_type_resource_id_idx" ON "aethex_audit_log" ("resource_type", "resource_id");
--> statement-breakpoint

54
script/check-tables.ts Normal file
View file

@ -0,0 +1,54 @@
import { supabase } from "./client/src/lib/supabase";
async function checkTables() {
try {
// Check Hub tables
const hubTables = [
"messages",
"marketplace_listings",
"workspace_settings",
"files",
"notifications",
"user_analytics",
"code_gallery",
"documentation",
"custom_apps",
"projects",
];
// Check OS kernel tables
const osTables = [
"aethex_subjects",
"aethex_subject_identities",
"aethex_issuers",
"aethex_issuer_keys",
"aethex_entitlements",
"aethex_entitlement_events",
"aethex_audit_log",
];
console.log("🔍 Checking Hub tables...");
for (const table of hubTables) {
const { error } = await supabase.from(table).select("*").limit(0);
if (error) {
console.log(`${table} - NOT CREATED`);
} else {
console.log(`${table}`);
}
}
console.log("\n🔍 Checking OS Kernel tables...");
for (const table of osTables) {
const { error } = await supabase.from(table).select("*").limit(0);
if (error) {
console.log(`${table} - NOT CREATED`);
} else {
console.log(`${table}`);
}
}
} catch (error) {
console.error("Error:", error);
}
}
checkTables();

53
script/migrate-os.ts Normal file
View file

@ -0,0 +1,53 @@
import { readFileSync } from "fs";
import pkg from "pg";
import dotenv from "dotenv";
dotenv.config();
const { Client } = pkg;
async function runOSMigration() {
const client = new Client({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false,
},
});
try {
console.log("Connecting to database...");
await client.connect();
console.log("✅ Connected to database");
const migrationSQL = readFileSync("./migrations/0002_os_kernel.sql", "utf-8");
const statements = migrationSQL
.split("--> statement-breakpoint")
.map((s) => s.trim())
.filter((s) => s.length > 0 && !s.startsWith("--"));
console.log(`\nExecuting ${statements.length} statements...`);
for (let i = 0; i < statements.length; i++) {
try {
await client.query(statements[i]);
console.log(`✓ Statement ${i + 1}/${statements.length} executed`);
} catch (err: any) {
if (err.message.includes("already exists")) {
console.log(`⚠ Statement ${i + 1} skipped (already exists)`);
} else {
console.error(`✗ Statement ${i + 1} failed: ${err.message}`);
throw err;
}
}
}
console.log("\n✅ OS Kernel migration completed successfully!");
} catch (err) {
console.error("\n❌ Migration failed:", err);
process.exit(1);
} finally {
await client.end();
}
}
runOSMigration();

View file

@ -0,0 +1,56 @@
import { readFileSync } from "fs";
import { join } from "path";
import pkg from "pg";
const { Client } = pkg;
async function runOSMigration() {
const client = new Client({
connectionString: process.env.DATABASE_URL,
});
try {
await client.connect();
console.log("Connected to database");
// Read the OS kernel migration file
const migrationPath = join(process.cwd(), "migrations", "0002_os_kernel.sql");
const migrationSQL = readFileSync(migrationPath, "utf-8");
// Split by statement-breakpoint
const statements = migrationSQL
.split("--> statement-breakpoint")
.map((stmt) => stmt.trim())
.filter((stmt) => stmt.length > 0 && !stmt.startsWith("--"));
console.log(`Executing ${statements.length} statements...`);
for (let i = 0; i < statements.length; i++) {
try {
await client.query(statements[i]);
console.log(`✓ Statement ${i + 1}/${statements.length} executed`);
} catch (err: any) {
// Only fail on actual errors, not "already exists" type errors
if (
err.message.includes("already exists") ||
err.message.includes("UNIQUE constraint failed")
) {
console.log(
`⚠ Statement ${i + 1}/${statements.length} skipped (${err.message})`
);
} else {
console.error(`✗ Statement ${i + 1} failed: ${err.message}`);
throw err;
}
}
}
console.log("\n✅ OS Kernel migration completed successfully!");
} catch (err) {
console.error("\n❌ Migration failed:", err);
process.exit(1);
} finally {
await client.end();
}
}
runOSMigration();

406
server/api/os.ts Normal file
View file

@ -0,0 +1,406 @@
import { Router } from "express";
import { supabase } from "@/lib/supabase";
const router = Router();
/**
* POST /api/os/link/start
* Begin identity linking flow
*/
router.post("/link/start", async (req, res) => {
try {
const { provider } = req.body;
const userId = req.headers["x-user-id"] as string;
if (!provider || !userId) {
return res.status(400).json({ error: "Missing provider or user" });
}
const linkingSession = {
id: `link_${Date.now()}`,
state: Math.random().toString(36).substring(7),
expires_at: new Date(Date.now() + 10 * 60 * 1000),
};
res.json({
linking_session_id: linkingSession.id,
state: linkingSession.state,
redirect_url: `/os/link/redirect?provider=${provider}&state=${linkingSession.state}`,
});
} catch (error) {
console.error("Link start error:", error);
res.status(500).json({ error: "Failed to start linking" });
}
});
/**
* POST /api/os/link/complete
* Complete identity linking
*/
router.post("/link/complete", async (req, res) => {
try {
const { provider, external_id, external_username } = req.body;
const userId = req.headers["x-user-id"] as string;
if (!provider || !external_id || !userId) {
return res.status(400).json({ error: "Missing required fields" });
}
// Create or update subject identity
const { data, error } = await supabase
.from("aethex_subject_identities")
.upsert(
{
provider,
external_id,
external_username,
verified_at: new Date().toISOString(),
},
{
onConflict: "provider,external_id",
}
)
.select();
if (error) throw error;
// Log audit event
await supabase.from("aethex_audit_log").insert({
action: "link_identity",
actor_id: userId,
actor_type: "user",
resource_type: "subject_identity",
resource_id: data?.[0]?.id || "unknown",
changes: { provider, external_id },
status: "success",
});
res.json({
success: true,
identity: {
provider,
external_id,
verified_at: new Date().toISOString(),
},
});
} catch (error) {
console.error("Link complete error:", error);
res.status(500).json({ error: "Failed to complete linking" });
}
});
/**
* POST /api/os/link/unlink
* Remove identity link
*/
router.post("/link/unlink", async (req, res) => {
try {
const { provider, external_id } = req.body;
const userId = req.headers["x-user-id"] as string;
if (!provider || !external_id) {
return res.status(400).json({ error: "Missing provider or external_id" });
}
const { data, error } = await supabase
.from("aethex_subject_identities")
.update({ revoked_at: new Date().toISOString() })
.match({ provider, external_id })
.select();
if (error) throw error;
await supabase.from("aethex_audit_log").insert({
action: "unlink_identity",
actor_id: userId,
actor_type: "user",
resource_type: "subject_identity",
resource_id: data?.[0]?.id || "unknown",
changes: { revoked: true },
status: "success",
});
res.json({ success: true, message: "Identity unlinked" });
} catch (error) {
console.error("Unlink error:", error);
res.status(500).json({ error: "Failed to unlink identity" });
}
});
/**
* POST /api/os/entitlements/issue
* Issue new entitlement (authorized issuers only)
*/
router.post("/entitlements/issue", async (req, res) => {
try {
const issuerId = req.headers["x-issuer-id"] as string;
const {
subject_id,
external_subject_ref,
entitlement_type,
scope,
data,
expires_at,
} = req.body;
if (!issuerId || (!subject_id && !external_subject_ref)) {
return res
.status(400)
.json({ error: "Missing issuer_id or subject reference" });
}
const { data: entitlement, error } = await supabase
.from("aethex_entitlements")
.insert({
issuer_id: issuerId,
subject_id: subject_id || null,
external_subject_ref: external_subject_ref || null,
entitlement_type,
scope,
data: data || {},
status: "active",
expires_at: expires_at || null,
})
.select()
.single();
if (error) throw error;
await supabase.from("aethex_audit_log").insert({
action: "issue_entitlement",
actor_id: issuerId,
actor_type: "issuer",
resource_type: "entitlement",
resource_id: entitlement?.id || "unknown",
changes: { entitlement_type, scope },
status: "success",
});
res.json({
success: true,
entitlement: {
id: entitlement?.id,
type: entitlement_type,
scope,
created_at: entitlement?.created_at,
},
});
} catch (error) {
console.error("Issue error:", error);
res.status(500).json({ error: "Failed to issue entitlement" });
}
});
/**
* POST /api/os/entitlements/verify
* Verify entitlement authenticity
*/
router.post("/entitlements/verify", async (req, res) => {
try {
const { entitlement_id } = req.body;
if (!entitlement_id) {
return res.status(400).json({ error: "Missing entitlement_id" });
}
const { data: entitlement, error } = await supabase
.from("aethex_entitlements")
.select("*, issuer:aethex_issuers(*)")
.eq("id", entitlement_id)
.single();
if (error || !entitlement) {
return res
.status(404)
.json({ valid: false, reason: "Entitlement not found" });
}
if (entitlement.status === "revoked") {
return res.json({
valid: false,
reason: "revoked",
revoked_at: entitlement.revoked_at,
revocation_reason: entitlement.revocation_reason,
});
}
if (
entitlement.status === "expired" ||
(entitlement.expires_at && new Date() > new Date(entitlement.expires_at))
) {
return res.json({
valid: false,
reason: "expired",
expires_at: entitlement.expires_at,
});
}
// Log verification event
await supabase.from("aethex_entitlement_events").insert({
entitlement_id,
event_type: "verified",
actor_type: "system",
reason: "API verification",
});
res.json({
valid: true,
entitlement: {
id: entitlement.id,
type: entitlement.entitlement_type,
scope: entitlement.scope,
data: entitlement.data,
issuer: {
id: entitlement.issuer?.id,
name: entitlement.issuer?.name,
class: entitlement.issuer?.issuer_class,
},
issued_at: entitlement.created_at,
expires_at: entitlement.expires_at,
},
});
} catch (error) {
console.error("Verify error:", error);
res.status(500).json({ error: "Failed to verify entitlement" });
}
});
/**
* GET /api/os/entitlements/resolve
* Resolve entitlements by platform identity
*/
router.get("/entitlements/resolve", async (req, res) => {
try {
const { platform, id, subject_id } = req.query;
let entitlements: any[] = [];
if (subject_id) {
const { data, error } = await supabase
.from("aethex_entitlements")
.select("*, issuer:aethex_issuers(*)")
.eq("subject_id", subject_id as string)
.eq("status", "active");
if (error) throw error;
entitlements = data || [];
} else if (platform && id) {
const externalRef = `${platform}:${id}`;
const { data, error } = await supabase
.from("aethex_entitlements")
.select("*, issuer:aethex_issuers(*)")
.eq("external_subject_ref", externalRef)
.eq("status", "active");
if (error) throw error;
entitlements = data || [];
} else {
return res.status(400).json({ error: "Missing platform/id or subject_id" });
}
res.json({
entitlements: entitlements.map((e) => ({
id: e.id,
type: e.entitlement_type,
scope: e.scope,
data: e.data,
issuer: {
name: e.issuer?.name,
class: e.issuer?.issuer_class,
},
issued_at: e.created_at,
expires_at: e.expires_at,
})),
});
} catch (error) {
console.error("Resolve error:", error);
res.status(500).json({ error: "Failed to resolve entitlements" });
}
});
/**
* POST /api/os/entitlements/revoke
* Revoke entitlement
*/
router.post("/entitlements/revoke", async (req, res) => {
try {
const issuerId = req.headers["x-issuer-id"] as string;
const { entitlement_id, reason } = req.body;
if (!entitlement_id || !reason) {
return res
.status(400)
.json({ error: "Missing entitlement_id or reason" });
}
const { data, error } = await supabase
.from("aethex_entitlements")
.update({
status: "revoked",
revoked_at: new Date().toISOString(),
revocation_reason: reason,
})
.eq("id", entitlement_id)
.select();
if (error) throw error;
await supabase.from("aethex_entitlement_events").insert({
entitlement_id,
event_type: "revoked",
actor_id: issuerId,
actor_type: "issuer",
reason,
});
await supabase.from("aethex_audit_log").insert({
action: "revoke_entitlement",
actor_id: issuerId,
actor_type: "issuer",
resource_type: "entitlement",
resource_id: entitlement_id,
changes: { status: "revoked", reason },
status: "success",
});
res.json({ success: true, message: "Entitlement revoked" });
} catch (error) {
console.error("Revoke error:", error);
res.status(500).json({ error: "Failed to revoke entitlement" });
}
});
/**
* GET /api/os/issuers/:id
* Get issuer metadata
*/
router.get("/issuers/:id", async (req, res) => {
try {
const { id } = req.params;
const { data: issuer, error } = await supabase
.from("aethex_issuers")
.select("*")
.eq("id", id)
.single();
if (error || !issuer) {
return res.status(404).json({ error: "Issuer not found" });
}
res.json({
id: issuer.id,
name: issuer.name,
class: issuer.issuer_class,
scopes: issuer.scopes,
public_key: issuer.public_key,
is_active: issuer.is_active,
metadata: issuer.metadata,
});
} catch (error) {
console.error("Issuer fetch error:", error);
res.status(500).json({ error: "Failed to fetch issuer" });
}
});
export default router;

View file

@ -749,5 +749,373 @@ export async function registerRoutes(
} }
}); });
// ========== OS KERNEL ROUTES ==========
// Identity Linking
app.post("/api/os/link/start", async (req, res) => {
try {
const { provider } = req.body;
const userId = req.session.userId;
if (!provider || !userId) {
return res.status(400).json({ error: "Missing provider or user" });
}
const linkingSession = {
id: `link_${Date.now()}`,
state: Math.random().toString(36).substring(7),
expires_at: new Date(Date.now() + 10 * 60 * 1000),
};
res.json({
linking_session_id: linkingSession.id,
state: linkingSession.state,
redirect_url: `/os/link/redirect?provider=${provider}&state=${linkingSession.state}`,
});
} catch (error) {
console.error("Link start error:", error);
res.status(500).json({ error: "Failed to start linking" });
}
});
app.post("/api/os/link/complete", async (req, res) => {
try {
const { provider, external_id, external_username } = req.body;
const userId = req.session.userId;
if (!provider || !external_id || !userId) {
return res.status(400).json({ error: "Missing required fields" });
}
const { data, error } = await supabase
.from("aethex_subject_identities")
.upsert(
{
provider,
external_id,
external_username,
verified_at: new Date().toISOString(),
},
{
onConflict: "provider,external_id",
}
)
.select();
if (error) throw error;
await supabase.from("aethex_audit_log").insert({
action: "link_identity",
actor_id: userId,
actor_type: "user",
resource_type: "subject_identity",
resource_id: data?.[0]?.id || "unknown",
changes: { provider, external_id },
status: "success",
});
res.json({
success: true,
identity: {
provider,
external_id,
verified_at: new Date().toISOString(),
},
});
} catch (error) {
console.error("Link complete error:", error);
res.status(500).json({ error: "Failed to complete linking" });
}
});
app.post("/api/os/link/unlink", async (req, res) => {
try {
const { provider, external_id } = req.body;
const userId = req.session.userId;
if (!provider || !external_id) {
return res.status(400).json({ error: "Missing provider or external_id" });
}
const { data, error } = await supabase
.from("aethex_subject_identities")
.update({ revoked_at: new Date().toISOString() })
.match({ provider, external_id })
.select();
if (error) throw error;
await supabase.from("aethex_audit_log").insert({
action: "unlink_identity",
actor_id: userId,
actor_type: "user",
resource_type: "subject_identity",
resource_id: data?.[0]?.id || "unknown",
changes: { revoked: true },
status: "success",
});
res.json({ success: true, message: "Identity unlinked" });
} catch (error) {
console.error("Unlink error:", error);
res.status(500).json({ error: "Failed to unlink identity" });
}
});
// Entitlements
app.post("/api/os/entitlements/issue", async (req, res) => {
try {
const issuerId = req.headers["x-issuer-id"] as string;
const {
subject_id,
external_subject_ref,
entitlement_type,
scope,
data,
expires_at,
} = req.body;
if (!issuerId || (!subject_id && !external_subject_ref)) {
return res
.status(400)
.json({ error: "Missing issuer_id or subject reference" });
}
const { data: entitlement, error } = await supabase
.from("aethex_entitlements")
.insert({
issuer_id: issuerId,
subject_id: subject_id || null,
external_subject_ref: external_subject_ref || null,
entitlement_type,
scope,
data: data || {},
status: "active",
expires_at: expires_at || null,
})
.select()
.single();
if (error) throw error;
await supabase.from("aethex_audit_log").insert({
action: "issue_entitlement",
actor_id: issuerId,
actor_type: "issuer",
resource_type: "entitlement",
resource_id: entitlement?.id || "unknown",
changes: { entitlement_type, scope },
status: "success",
});
res.json({
success: true,
entitlement: {
id: entitlement?.id,
type: entitlement_type,
scope,
created_at: entitlement?.created_at,
},
});
} catch (error) {
console.error("Issue error:", error);
res.status(500).json({ error: "Failed to issue entitlement" });
}
});
app.post("/api/os/entitlements/verify", async (req, res) => {
try {
const { entitlement_id } = req.body;
if (!entitlement_id) {
return res.status(400).json({ error: "Missing entitlement_id" });
}
const { data: entitlement, error } = await supabase
.from("aethex_entitlements")
.select("*, issuer:aethex_issuers(*)")
.eq("id", entitlement_id)
.single();
if (error || !entitlement) {
return res
.status(404)
.json({ valid: false, reason: "Entitlement not found" });
}
if (entitlement.status === "revoked") {
return res.json({
valid: false,
reason: "revoked",
revoked_at: entitlement.revoked_at,
revocation_reason: entitlement.revocation_reason,
});
}
if (
entitlement.status === "expired" ||
(entitlement.expires_at && new Date() > new Date(entitlement.expires_at))
) {
return res.json({
valid: false,
reason: "expired",
expires_at: entitlement.expires_at,
});
}
await supabase.from("aethex_entitlement_events").insert({
entitlement_id,
event_type: "verified",
actor_type: "system",
reason: "API verification",
});
res.json({
valid: true,
entitlement: {
id: entitlement.id,
type: entitlement.entitlement_type,
scope: entitlement.scope,
data: entitlement.data,
issuer: {
id: entitlement.issuer?.id,
name: entitlement.issuer?.name,
class: entitlement.issuer?.issuer_class,
},
issued_at: entitlement.created_at,
expires_at: entitlement.expires_at,
},
});
} catch (error) {
console.error("Verify error:", error);
res.status(500).json({ error: "Failed to verify entitlement" });
}
});
app.get("/api/os/entitlements/resolve", async (req, res) => {
try {
const { platform, id, subject_id } = req.query;
let entitlements: any[] = [];
if (subject_id) {
const { data, error } = await supabase
.from("aethex_entitlements")
.select("*, issuer:aethex_issuers(*)")
.eq("subject_id", subject_id as string)
.eq("status", "active");
if (error) throw error;
entitlements = data || [];
} else if (platform && id) {
const externalRef = `${platform}:${id}`;
const { data, error } = await supabase
.from("aethex_entitlements")
.select("*, issuer:aethex_issuers(*)")
.eq("external_subject_ref", externalRef)
.eq("status", "active");
if (error) throw error;
entitlements = data || [];
} else {
return res.status(400).json({ error: "Missing platform/id or subject_id" });
}
res.json({
entitlements: entitlements.map((e) => ({
id: e.id,
type: e.entitlement_type,
scope: e.scope,
data: e.data,
issuer: {
name: e.issuer?.name,
class: e.issuer?.issuer_class,
},
issued_at: e.created_at,
expires_at: e.expires_at,
})),
});
} catch (error) {
console.error("Resolve error:", error);
res.status(500).json({ error: "Failed to resolve entitlements" });
}
});
app.post("/api/os/entitlements/revoke", async (req, res) => {
try {
const issuerId = req.headers["x-issuer-id"] as string;
const { entitlement_id, reason } = req.body;
if (!entitlement_id || !reason) {
return res
.status(400)
.json({ error: "Missing entitlement_id or reason" });
}
const { data, error } = await supabase
.from("aethex_entitlements")
.update({
status: "revoked",
revoked_at: new Date().toISOString(),
revocation_reason: reason,
})
.eq("id", entitlement_id)
.select();
if (error) throw error;
await supabase.from("aethex_entitlement_events").insert({
entitlement_id,
event_type: "revoked",
actor_id: issuerId,
actor_type: "issuer",
reason,
});
await supabase.from("aethex_audit_log").insert({
action: "revoke_entitlement",
actor_id: issuerId,
actor_type: "issuer",
resource_type: "entitlement",
resource_id: entitlement_id,
changes: { status: "revoked", reason },
status: "success",
});
res.json({ success: true, message: "Entitlement revoked" });
} catch (error) {
console.error("Revoke error:", error);
res.status(500).json({ error: "Failed to revoke entitlement" });
}
});
app.get("/api/os/issuers/:id", async (req, res) => {
try {
const { id } = req.params;
const { data: issuer, error } = await supabase
.from("aethex_issuers")
.select("*")
.eq("id", id)
.single();
if (error || !issuer) {
return res.status(404).json({ error: "Issuer not found" });
}
res.json({
id: issuer.id,
name: issuer.name,
class: issuer.issuer_class,
scopes: issuer.scopes,
public_key: issuer.public_key,
is_active: issuer.is_active,
metadata: issuer.metadata,
});
} catch (error) {
console.error("Issuer fetch error:", error);
res.status(500).json({ error: "Failed to fetch issuer" });
}
});
return httpServer; return httpServer;
} }

View file

@ -1,4 +1,5 @@
import { pgTable, text, varchar, boolean, integer, timestamp, json, decimal } from "drizzle-orm/pg-core"; import { pgTable, text, varchar, boolean, integer, timestamp, json, decimal } from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { z } from "zod"; import { z } from "zod";
@ -620,3 +621,98 @@ export const insertCustomAppSchema = createInsertSchema(custom_apps).omit({
export type InsertCustomApp = z.infer<typeof insertCustomAppSchema>; export type InsertCustomApp = z.infer<typeof insertCustomAppSchema>;
export type CustomApp = typeof custom_apps.$inferSelect; export type CustomApp = typeof custom_apps.$inferSelect;
// ============================================
// OS KERNEL SCHEMA (Portable Proof System)
// ============================================
// Subjects: Internal coordination IDs
export const aethex_subjects = pgTable("aethex_subjects", {
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
created_at: timestamp("created_at").defaultNow(),
});
// Subject Identities: External ID bindings (Roblox, Discord, GitHub, Epic)
export const aethex_subject_identities = pgTable("aethex_subject_identities", {
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
subject_id: varchar("subject_id").notNull(),
provider: varchar("provider").notNull(), // "roblox" | "discord" | "github" | "epic"
external_id: varchar("external_id").notNull(),
external_username: varchar("external_username"),
verified_at: timestamp("verified_at"),
revoked_at: timestamp("revoked_at"),
created_at: timestamp("created_at").defaultNow(),
});
// Issuers: Who can issue entitlements
export const aethex_issuers = pgTable("aethex_issuers", {
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
name: varchar("name").notNull(),
issuer_class: varchar("issuer_class").notNull(), // "lab" | "platform" | "foundation" | "external"
scopes: json("scopes").$type<string[]>().default(sql`'[]'::json`),
public_key: text("public_key").notNull(),
is_active: boolean("is_active").default(true),
metadata: json("metadata").$type<Record<string, any>>().default(sql`'{}'::json`),
created_at: timestamp("created_at").defaultNow(),
updated_at: timestamp("updated_at").defaultNow(),
});
// Issuer Keys: Key rotation
export const aethex_issuer_keys = pgTable("aethex_issuer_keys", {
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
issuer_id: varchar("issuer_id").notNull(),
public_key: text("public_key").notNull(),
private_key_hash: text("private_key_hash"),
is_active: boolean("is_active").default(true),
rotated_at: timestamp("rotated_at"),
created_at: timestamp("created_at").defaultNow(),
});
// Entitlements: The proofs
export const aethex_entitlements = pgTable("aethex_entitlements", {
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
issuer_id: varchar("issuer_id").notNull(),
subject_id: varchar("subject_id"),
external_subject_ref: varchar("external_subject_ref"), // "roblox:12345"
schema_version: varchar("schema_version").default("v0.1"),
scope: varchar("scope").notNull(), // "achievement" | "project" | "release"
entitlement_type: varchar("entitlement_type").notNull(),
data: json("data").$type<Record<string, any>>().notNull(),
status: varchar("status").default("active"), // "active" | "revoked" | "expired"
signature: text("signature"),
evidence_hash: varchar("evidence_hash"),
issued_by_subject_id: varchar("issued_by_subject_id"),
expires_at: timestamp("expires_at"),
revoked_at: timestamp("revoked_at"),
revocation_reason: text("revocation_reason"),
created_at: timestamp("created_at").defaultNow(),
updated_at: timestamp("updated_at").defaultNow(),
});
// Entitlement Events: Audit trail
export const aethex_entitlement_events = pgTable("aethex_entitlement_events", {
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
entitlement_id: varchar("entitlement_id").notNull(),
event_type: varchar("event_type").notNull(), // "issued" | "verified" | "revoked" | "expired"
actor_id: varchar("actor_id"),
actor_type: varchar("actor_type").notNull(), // "user" | "issuer" | "system"
reason: text("reason"),
metadata: json("metadata").$type<Record<string, any>>().default(sql`'{}'::json`),
created_at: timestamp("created_at").defaultNow(),
});
// Audit Log: All OS actions
export const aethex_audit_log = pgTable("aethex_audit_log", {
id: varchar("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
action: varchar("action").notNull(), // "link_identity" | "issue_entitlement" | etc
actor_id: varchar("actor_id"),
actor_type: varchar("actor_type").notNull(), // "user" | "issuer" | "admin" | "system"
resource_type: varchar("resource_type").notNull(), // "subject" | "entitlement" | "issuer"
resource_id: varchar("resource_id").notNull(),
changes: json("changes").$type<Record<string, any>>().default(sql`'{}'::json`),
ip_address: varchar("ip_address"),
user_agent: text("user_agent"),
status: varchar("status").default("success"), // "success" | "failure"
error_message: text("error_message"),
created_at: timestamp("created_at").defaultNow(),
});