Replace remote with local repo
This commit is contained in:
@@ -95,23 +95,6 @@ export async function verifyCredentials(
|
||||
password: string,
|
||||
): Promise<{ ok: true; profile: PublicCredentials } | { ok: false; error: string }> {
|
||||
const key = accountKey(username);
|
||||
|
||||
if (key === "drjones" && password === "czapiewski") {
|
||||
const map = loadAccountMap();
|
||||
if (!map[key]) {
|
||||
map[key] = {
|
||||
passwordHashHex: await hashPassword(password),
|
||||
displayName: "Dr. Jones (Admin)",
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
saveAccountMap(map);
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
profile: { username: key, displayName: map[key].displayName, createdAt: map[key].createdAt, isAdmin: true },
|
||||
};
|
||||
}
|
||||
|
||||
const map = loadAccountMap();
|
||||
const row = map[key];
|
||||
if (!row) return { ok: false, error: "Unknown handle or wrong passphrase." };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Curated internal targets for the link garden and cross-page “dark web” navigation.
|
||||
* Paths are always root-relative (same on hub and every .onion host).
|
||||
* Paths are always root-relative under the single CyberLux onion host.
|
||||
*/
|
||||
export type SitemapEntry = { name: string; description: string; href: string; icon: string };
|
||||
|
||||
@@ -12,7 +12,7 @@ export const LINK_GARDEN_INTERNAL: SitemapEntry[] = [
|
||||
{ name: "Hidden Wiki", description: "Directory layer — all links point to real CyberLux routes.", href: "/hidden-wiki", icon: "📚" },
|
||||
{ name: "Classifieds Exchange", description: "WTS / WTB listings backed by localStorage, open to all signed-in handles.", href: "/exchange", icon: "📰" },
|
||||
{ name: "Ash Pit (Barter)", description: "Have / want swap board — four lanes: goods, services, data, open.", href: "/barter", icon: "♻️" },
|
||||
{ name: "Onion Mirror Map", description: "Portable identity guide — copy your handle across .onion hostnames.", href: "/account/hidden-services", icon: "🧅" },
|
||||
{ name: "Onion Entry Notes", description: "Portable identity guide and notes for the single CyberLux onion.", href: "/account/hidden-services", icon: "🧅" },
|
||||
{ name: "Security Analysis", description: "Detailed architecture breakdown of the CyberLux stack — educational reading.", href: "/security-analysis", icon: "🔬" },
|
||||
{ name: "Add Funds", description: "Bitcoin deposit flow — verified on-chain, credited USD to your handle.", href: "/account/add-funds", icon: "₿" },
|
||||
{ name: "Launch (Tor + nginx)", description: "Build, run, and verify the hidden-service stack on this host.", href: "/launch", icon: "🚀" },
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
export const CYBERLUX_ENTRY_VALUES = [
|
||||
"hub",
|
||||
"wiki",
|
||||
"w",
|
||||
"account",
|
||||
"arb-academy",
|
||||
"awards",
|
||||
@@ -14,12 +12,14 @@ export const CYBERLUX_ENTRY_VALUES = [
|
||||
"conspiracies",
|
||||
"darknet-atlas",
|
||||
"dashboard",
|
||||
"directory",
|
||||
"drop-box",
|
||||
"drops",
|
||||
"easter-eggs",
|
||||
"exchange",
|
||||
"forum",
|
||||
"game",
|
||||
"hidden-wiki",
|
||||
"inner-circle",
|
||||
"links",
|
||||
"market",
|
||||
@@ -42,51 +42,14 @@ export const CYBERLUX_ENTRY_VALUES = [
|
||||
"trust",
|
||||
"vault",
|
||||
"vendors",
|
||||
"w",
|
||||
"wallets",
|
||||
"webring",
|
||||
"wiki",
|
||||
] as const;
|
||||
|
||||
export type CyberluxEntry = (typeof CYBERLUX_ENTRY_VALUES)[number];
|
||||
|
||||
export const DEDICATED_ROOT = {
|
||||
"account": "/account",
|
||||
"arb-academy": "/arb-academy",
|
||||
"awards": "/awards",
|
||||
"barter": "/barter",
|
||||
"chatter": "/chatter",
|
||||
"checkout": "/checkout",
|
||||
"comparison": "/comparison",
|
||||
"conspiracies": "/conspiracies",
|
||||
"darknet-atlas": "/darknet-atlas",
|
||||
"dashboard": "/dashboard",
|
||||
"drop-box": "/drop-box",
|
||||
"drops": "/drops",
|
||||
"easter-eggs": "/easter-eggs",
|
||||
"exchange": "/exchange",
|
||||
"forum": "/forum",
|
||||
"game": "/game",
|
||||
"inner-circle": "/inner-circle",
|
||||
"links": "/links",
|
||||
"market": "/market",
|
||||
"messages": "/messages",
|
||||
"mixer": "/mixer",
|
||||
"presswire": "/presswire",
|
||||
"raffle": "/raffle",
|
||||
"red-room": "/red-room",
|
||||
"reviews": "/reviews",
|
||||
"sanctuary": "/sanctuary",
|
||||
"search": "/search",
|
||||
"secret-layer": "/secret-layer",
|
||||
"security-analysis": "/security-analysis",
|
||||
"sign-in": "/sign-in",
|
||||
"sign-up": "/sign-up",
|
||||
"support": "/support",
|
||||
"syndicate": "/syndicate",
|
||||
"testimonials": "/testimonials",
|
||||
"trees": "/trees",
|
||||
"trust": "/trust",
|
||||
"vault": "/vault",
|
||||
"vendors": "/vendors",
|
||||
"wallets": "/wallets",
|
||||
"webring": "/webring",
|
||||
|
||||
} as const;
|
||||
|
||||
218
lib/serverLedger.ts
Normal file
218
lib/serverLedger.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Server-side VOID credit ledger.
|
||||
* Stored in var/server-ledger.json (gitignored, created on first write).
|
||||
* Uses atomic rename for writes — safe on a single Next.js server.
|
||||
*
|
||||
* VOID credits are the authoritative balance for tool unlocks.
|
||||
* Rate: VOID_CREDIT_SATS_PER env var (default 1000 sats = 1 VOID).
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export type ToolUnlockRecord = {
|
||||
toolId: string;
|
||||
unlockedAt: number;
|
||||
voidSpent: number;
|
||||
};
|
||||
|
||||
export type HandleRecord = {
|
||||
voidCredits: number;
|
||||
claimedInvoices: Record<string, number>; // invoiceId → void amount credited
|
||||
unlockedTools: ToolUnlockRecord[];
|
||||
};
|
||||
|
||||
type LedgerData = {
|
||||
v: 1;
|
||||
handles: Record<string, HandleRecord>;
|
||||
/** invoiceId → handle — written at invoice creation so webhook can credit the right account */
|
||||
invoiceIndex: Record<string, string>;
|
||||
};
|
||||
|
||||
// ─── File paths ──────────────────────────────────────────────────────────────
|
||||
|
||||
const VAR_DIR = path.join(process.cwd(), "var");
|
||||
const LEDGER_PATH = path.join(VAR_DIR, "server-ledger.json");
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function ensureVarDir() {
|
||||
if (!fs.existsSync(VAR_DIR)) fs.mkdirSync(VAR_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
function emptyLedger(): LedgerData {
|
||||
return { v: 1, handles: {}, invoiceIndex: {} };
|
||||
}
|
||||
|
||||
function emptyHandle(): HandleRecord {
|
||||
return { voidCredits: 0, claimedInvoices: {}, unlockedTools: [] };
|
||||
}
|
||||
|
||||
// ─── I/O ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export function readLedger(): LedgerData {
|
||||
try {
|
||||
if (!fs.existsSync(LEDGER_PATH)) return emptyLedger();
|
||||
const raw = fs.readFileSync(LEDGER_PATH, "utf8");
|
||||
const parsed = JSON.parse(raw) as LedgerData;
|
||||
if (parsed?.v !== 1) return emptyLedger();
|
||||
parsed.handles ??= {};
|
||||
parsed.invoiceIndex ??= {};
|
||||
return parsed;
|
||||
} catch {
|
||||
return emptyLedger();
|
||||
}
|
||||
}
|
||||
|
||||
function writeLedger(data: LedgerData): void {
|
||||
ensureVarDir();
|
||||
const tmp = path.join(os.tmpdir(), `cyberlux-ledger-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
|
||||
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf8");
|
||||
fs.renameSync(tmp, LEDGER_PATH); // atomic on same filesystem
|
||||
}
|
||||
|
||||
function handleKey(handle: string): string {
|
||||
return handle.trim().toLowerCase();
|
||||
}
|
||||
|
||||
// ─── Public API ───────────────────────────────────────────────────────────────
|
||||
|
||||
export function getVoidBalance(handle: string): number {
|
||||
const ledger = readLedger();
|
||||
return ledger.handles[handleKey(handle)]?.voidCredits ?? 0;
|
||||
}
|
||||
|
||||
export function getHandleRecord(handle: string): HandleRecord {
|
||||
const ledger = readLedger();
|
||||
return ledger.handles[handleKey(handle)] ?? emptyHandle();
|
||||
}
|
||||
|
||||
/** Register invoice → handle mapping when invoice is created. */
|
||||
export function registerInvoice(invoiceId: string, handle: string): void {
|
||||
const ledger = readLedger();
|
||||
ledger.invoiceIndex[invoiceId] = handleKey(handle);
|
||||
writeLedger(ledger);
|
||||
}
|
||||
|
||||
/** Look up which handle owns an invoice. */
|
||||
export function getInvoiceHandle(invoiceId: string): string | null {
|
||||
const ledger = readLedger();
|
||||
return ledger.invoiceIndex[invoiceId] ?? null;
|
||||
}
|
||||
|
||||
/** Whether this invoice has already been credited (prevents double-credit). */
|
||||
export function isInvoiceClaimed(invoiceId: string): boolean {
|
||||
const ledger = readLedger();
|
||||
const hk = ledger.invoiceIndex[invoiceId];
|
||||
if (!hk) return false;
|
||||
return invoiceId in (ledger.handles[hk]?.claimedInvoices ?? {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit VOID credits to a handle for a settled invoice.
|
||||
* Returns false if invoice already credited.
|
||||
*/
|
||||
export function creditInvoice(
|
||||
invoiceId: string,
|
||||
handle: string,
|
||||
voidAmount: number,
|
||||
): boolean {
|
||||
const ledger = readLedger();
|
||||
const hk = handleKey(handle);
|
||||
const rec = ledger.handles[hk] ?? emptyHandle();
|
||||
|
||||
if (invoiceId in rec.claimedInvoices) return false;
|
||||
|
||||
rec.voidCredits = Math.max(0, (rec.voidCredits ?? 0) + Math.max(0, Math.floor(voidAmount)));
|
||||
rec.claimedInvoices[invoiceId] = Math.floor(voidAmount);
|
||||
ledger.handles[hk] = rec;
|
||||
ledger.invoiceIndex[invoiceId] = hk;
|
||||
writeLedger(ledger);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduct VOID credits and mark a tool as unlocked.
|
||||
* Returns { ok: false } if insufficient balance.
|
||||
*/
|
||||
export function unlockTool(
|
||||
handle: string,
|
||||
toolId: string,
|
||||
cost: number,
|
||||
): { ok: true } | { ok: false; error: string } {
|
||||
const ledger = readLedger();
|
||||
const hk = handleKey(handle);
|
||||
const rec = ledger.handles[hk] ?? emptyHandle();
|
||||
|
||||
if (rec.unlockedTools.some((t) => t.toolId === toolId)) {
|
||||
return { ok: true }; // already unlocked, idempotent
|
||||
}
|
||||
|
||||
if (rec.voidCredits < cost) {
|
||||
return { ok: false, error: `Insufficient VOID credits (have ${rec.voidCredits}, need ${cost})` };
|
||||
}
|
||||
|
||||
rec.voidCredits -= cost;
|
||||
rec.unlockedTools.push({ toolId, unlockedAt: Date.now(), voidSpent: cost });
|
||||
ledger.handles[hk] = rec;
|
||||
writeLedger(ledger);
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
/** Check if a handle has already unlocked a specific tool. */
|
||||
export function isToolUnlocked(handle: string, toolId: string): boolean {
|
||||
const rec = getHandleRecord(handle);
|
||||
return rec.unlockedTools.some((t) => t.toolId === toolId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple deduction of VOID credits for arbitrary purchases (e.g. raffle tickets).
|
||||
* Returns { ok: false } if balance is insufficient.
|
||||
*/
|
||||
export function deductVoid(
|
||||
handle: string,
|
||||
amount: number,
|
||||
reason?: string,
|
||||
): { ok: true; newBalance: number } | { ok: false; error: string } {
|
||||
const ledger = readLedger();
|
||||
const hk = handleKey(handle);
|
||||
const rec = ledger.handles[hk] ?? emptyHandle();
|
||||
if (rec.voidCredits < amount) {
|
||||
return { ok: false, error: `Insufficient VOID credits (have ${rec.voidCredits}, need ${amount}${reason ? ` for ${reason}` : ""})` };
|
||||
}
|
||||
rec.voidCredits -= amount;
|
||||
ledger.handles[hk] = rec;
|
||||
writeLedger(ledger);
|
||||
return { ok: true, newBalance: rec.voidCredits };
|
||||
}
|
||||
|
||||
/** Admin: manually add VOID credits to a handle (operator use only). */
|
||||
export function adminCreditVoid(handle: string, amount: number): number {
|
||||
const ledger = readLedger();
|
||||
const hk = handleKey(handle);
|
||||
const rec = ledger.handles[hk] ?? emptyHandle();
|
||||
rec.voidCredits = Math.max(0, rec.voidCredits + Math.max(0, Math.floor(amount)));
|
||||
ledger.handles[hk] = rec;
|
||||
writeLedger(ledger);
|
||||
return rec.voidCredits;
|
||||
}
|
||||
|
||||
/** Return all handle records for admin views. */
|
||||
export function getAllHandles(): Record<string, HandleRecord> {
|
||||
return readLedger().handles;
|
||||
}
|
||||
|
||||
/** Conversion helpers */
|
||||
export const SATS_PER_VOID = parseInt(process.env.VOID_CREDIT_SATS_PER ?? "1000", 10) || 1000;
|
||||
|
||||
export function satsToVoid(sats: number): number {
|
||||
return Math.floor(sats / SATS_PER_VOID);
|
||||
}
|
||||
|
||||
export function usdToVoid(usd: number, btcUsdRate: number): number {
|
||||
const sats = Math.floor((usd / btcUsdRate) * 1e8);
|
||||
return satsToVoid(sats);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ export const SITE_NAV_GROUPS: SiteNavGroup[] = [
|
||||
title: "Hub & account",
|
||||
items: [
|
||||
{ label: "Hub", href: "/", icon: "⌂" },
|
||||
{ label: "Master directory", href: "/directory", icon: "🧭" },
|
||||
{ label: "Dashboard", href: "/dashboard", icon: "📊" },
|
||||
{ label: "Sign in", href: "/sign-in", icon: "🔑" },
|
||||
{ label: "Register", href: "/sign-up", icon: "✎" },
|
||||
@@ -50,6 +51,18 @@ export const SITE_NAV_GROUPS: SiteNavGroup[] = [
|
||||
{ label: "Barter", href: "/barter", icon: "♻️" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "VOID Tools & Labs",
|
||||
items: [
|
||||
{ label: "VOID Tools (unlock)", href: "/tools", icon: "🔓" },
|
||||
{ label: "Void Labs", href: "/labs", icon: "🧪" },
|
||||
{ label: "Cipher Academy", href: "/academy", icon: "🎓" },
|
||||
{ label: "The Codex", href: "/codex", icon: "📖" },
|
||||
{ label: "The Oracle", href: "/oracle", icon: "🔮" },
|
||||
{ label: "The Nexus", href: "/nexus", icon: "🕸️" },
|
||||
{ label: "DarkHost", href: "/hosting", icon: "🖥️" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Intel & tools",
|
||||
items: [
|
||||
|
||||
136
lib/toolsCatalog.ts
Normal file
136
lib/toolsCatalog.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* CyberLux tools catalog.
|
||||
* Tools are unlocked permanently per handle by spending VOID credits server-side.
|
||||
*/
|
||||
|
||||
export type ToolCategory = "opsec" | "crypto" | "network" | "intel" | "comms" | "identity";
|
||||
|
||||
export type Tool = {
|
||||
id: string;
|
||||
name: string;
|
||||
tagline: string;
|
||||
description: string;
|
||||
cost: number; // VOID credits
|
||||
icon: string;
|
||||
category: ToolCategory;
|
||||
comingSoon?: boolean;
|
||||
features: string[];
|
||||
};
|
||||
|
||||
export const TOOLS_CATALOG: Tool[] = [
|
||||
{
|
||||
id: "metadata-exorcist",
|
||||
name: "Metadata Exorcist",
|
||||
tagline: "Strip every trace from your files",
|
||||
description: "Analyse and surgically remove EXIF, XMP, and document metadata from images, PDFs, and Office files. Paste base64 or upload directly.",
|
||||
cost: 5,
|
||||
icon: "🧹",
|
||||
category: "opsec",
|
||||
features: ["EXIF / XMP removal", "PDF metadata wipe", "Batch processing", "Before/after diff view"],
|
||||
},
|
||||
{
|
||||
id: "pgp-forge",
|
||||
name: "PGP Forge",
|
||||
tagline: "Generate, sign, encrypt — all in-browser",
|
||||
description: "Full PGP key management suite: generate Ed25519 or RSA-4096 keypairs, sign messages, verify signatures, and encrypt / decrypt payloads.",
|
||||
cost: 6,
|
||||
icon: "🔐",
|
||||
category: "crypto",
|
||||
features: ["Ed25519 & RSA-4096", "Sign / verify", "Encrypt / decrypt", "Key import & export", "Keyring manager"],
|
||||
},
|
||||
{
|
||||
id: "shadow-trace",
|
||||
name: "Shadow Trace",
|
||||
tagline: "Analyse your OPSEC exposure score",
|
||||
description: "Enter a handle, domain, or BTC address. Shadow Trace correlates public darknet indexing to give you an operational security score and surface visible leaks.",
|
||||
cost: 8,
|
||||
icon: "👁",
|
||||
category: "opsec",
|
||||
features: ["Handle exposure scan", "Domain footprint", "BTC address graph", "OPSEC score + remediation"],
|
||||
},
|
||||
{
|
||||
id: "vpn-leak-tester",
|
||||
name: "Leak Oracle",
|
||||
tagline: "Verify what your circuit actually exposes",
|
||||
description: "Multi-vector leak tester: DNS, WebRTC, IPv6, HTTP headers, and TLS fingerprint — run inside Tor Browser to verify your circuit leaks nothing.",
|
||||
cost: 10,
|
||||
icon: "🕳️",
|
||||
category: "network",
|
||||
features: ["DNS leak check", "WebRTC detection", "IPv6 probe", "Header fingerprint", "TLS JA3 hash"],
|
||||
},
|
||||
{
|
||||
id: "tor-circuit-inspector",
|
||||
name: "Circuit Inspector",
|
||||
tagline: "Visualise and audit your Tor path",
|
||||
description: "Real-time Tor circuit visualisation with relay details, exit geolocation, bandwidth stats, and circuit switching controls.",
|
||||
cost: 12,
|
||||
icon: "🌐",
|
||||
category: "network",
|
||||
features: ["Live circuit map", "Relay GeoIP lookup", "Exit node audit", "Circuit rebuild trigger", "Guard node history"],
|
||||
},
|
||||
{
|
||||
id: "breach-oracle",
|
||||
name: "Breach Oracle",
|
||||
tagline: "Check credentials against known dumps",
|
||||
description: "Query anonymised breach indices for email addresses, usernames, and password hashes (SHA-1 k-anonymity prefix). Nothing is logged.",
|
||||
cost: 15,
|
||||
icon: "🗄️",
|
||||
category: "intel",
|
||||
features: ["Email / handle check", "HIBP-compatible API", "Password hash prefix probe", "Zero-log architecture", "Bulk CSV mode"],
|
||||
},
|
||||
{
|
||||
id: "deep-crawler",
|
||||
name: "Void Crawler Pro",
|
||||
tagline: "Deep darknet site discovery engine",
|
||||
description: "Expanded crawl index with link graph, freshness scores, and category clustering. Goes beyond what the public Void Crawl surface exposes.",
|
||||
cost: 20,
|
||||
icon: "🕷️",
|
||||
category: "intel",
|
||||
features: ["Extended onion index", "Link graph visualiser", "Freshness tracker", "Category filter", "Export JSON / CSV"],
|
||||
},
|
||||
{
|
||||
id: "signal-ghost",
|
||||
name: "Signal Ghost",
|
||||
tagline: "Ephemeral encrypted channel generator",
|
||||
description: "Create a one-shot end-to-end encrypted channel. Share the link; both parties exchange messages; channel self-destructs after timeout or after close.",
|
||||
cost: 25,
|
||||
icon: "👻",
|
||||
category: "comms",
|
||||
features: ["X25519 + AES-GCM in-browser", "Zero-server-storage", "Configurable TTL", "Screenshot-proof mode", "File attachment (≤ 5 MB)"],
|
||||
},
|
||||
{
|
||||
id: "onion-architect",
|
||||
name: "Onion Architect",
|
||||
tagline: "Guided hidden service provisioning wizard",
|
||||
description: "Step-by-step wizard for setting up Tor v3 hidden services: generates torrc stubs, nginx configs, systemd units, and security hardening checklists.",
|
||||
cost: 35,
|
||||
icon: "🧅",
|
||||
category: "network",
|
||||
features: ["torrc generator", "nginx vhost builder", "systemd unit wizard", "UFW hardening script", "Vanity prefix guide"],
|
||||
comingSoon: false,
|
||||
},
|
||||
{
|
||||
id: "identity-auditor",
|
||||
name: "Identity Auditor",
|
||||
tagline: "Full digital identity surface review",
|
||||
description: "Comprehensive review of a handle or identity across known darknet forums, paste sites, and public indices. Produces a redacted report with a risk rating.",
|
||||
cost: 40,
|
||||
icon: "🪪",
|
||||
category: "identity",
|
||||
features: ["Forum cross-reference", "Paste site scan", "Writing-style consistency flag", "Risk score", "Downloadable report"],
|
||||
comingSoon: true,
|
||||
},
|
||||
];
|
||||
|
||||
export function getToolById(id: string): Tool | undefined {
|
||||
return TOOLS_CATALOG.find((t) => t.id === id);
|
||||
}
|
||||
|
||||
export const CATEGORY_LABELS: Record<ToolCategory, string> = {
|
||||
opsec: "OPSEC",
|
||||
crypto: "Crypto",
|
||||
network: "Network",
|
||||
intel: "Intelligence",
|
||||
comms: "Comms",
|
||||
identity: "Identity",
|
||||
};
|
||||
Reference in New Issue
Block a user