This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
token-gated-economy/artifacts/api-server/src/index.ts
alexpaynex 31a843a829 task/31: Relay account whitelist + trust-gated access (v2 — code review fixes)
## What was built
Full relay access control: relay_accounts table, RelayAccountService,
trust hook, live policy enforcement, admin CRUD API, elite startup seed.

## DB schema (`lib/db/src/schema/relay-accounts.ts`)
relay_accounts table: pubkey (PK, FK nostr_identities ON DELETE CASCADE),
access_level (none/read/write), granted_by (text), granted_at, revoked_at, notes.
Exported from lib/db/src/schema/index.ts. Pushed via pnpm run push.

## RelayAccountService (`artifacts/api-server/src/lib/relay-accounts.ts`)
- getAccess(pubkey) → RelayAccessLevel (none if missing or revoked)
- grant(pubkey, level, reason, grantedBy) — upsert; creates nostr_identity FK
- revoke(pubkey, reason) — sets revokedAt, accessLevel→none, grantedBy→"manual-revoked"
  The "manual-revoked" marker prevents syncFromTrustTier from auto-reinstating.
  Only explicit admin grant() can restore access after revocation.
- syncFromTrustTier(pubkey) — fetches tier from DB internally (no tier param to
  avoid caller drift). Respects: manual-revoked (skip), manual active (upgrade only),
  auto-tier (full sync). Never auto-reinstates revoked accounts.
- seedElite(pubkey, notes) — upserts nostr_identities with tier="elite" +
  trustScore=200, then grants relay write access as a permanent manual grant.
  Called at startup for Timmy's own pubkey.
- list(opts) — returns all accounts, filtered by activeOnly if requested.
- Tier→access: new=none, established/trusted/elite=write (env-overridable)

## Trust hook (`artifacts/api-server/src/lib/trust.ts`)
recordSuccess + recordFailure both call syncFromTrustTier(pubkey) after DB write.
Fire-and-forget with catch (trust flow is never blocked by relay errors).

## Policy endpoint (`artifacts/api-server/src/routes/relay.ts`)
evaluatePolicy() async: queries relay_accounts.getAccess(). write→accept,
read/none/missing→reject. DB error→reject (fail-closed).

## Admin routes (`artifacts/api-server/src/routes/admin-relay.ts`)
ADMIN_SECRET Bearer auth (localhost-only fallback in dev; error log in prod).
GET  /api/admin/relay/accounts             — list all accounts
POST /api/admin/relay/accounts/:pk/grant   — grant (level + notes)
POST /api/admin/relay/accounts/:pk/revoke  — revoke (sets manual-revoked)
pubkey validation: 64-char lowercase hex only.

## Startup seed (`artifacts/api-server/src/index.ts`)
Resolves pubkey from TIMMY_NOSTR_PUBKEY env first, falls back to
timmyIdentityService.pubkeyHex. Calls seedElite() — idempotent upsert.
Sets nostr_identities.tier="elite" alongside relay write access.

## Smoke test results (all pass)
Timmy accept ✓; unknown reject ✓; grant→accept ✓; revoke→manual-revoked ✓;
revoked stays rejected ✓; TypeScript 0 errors.
2026-03-19 20:26:03 +00:00

52 lines
1.9 KiB
TypeScript

import { createServer } from "http";
import app from "./app.js";
import { attachWebSocketServer } from "./routes/events.js";
import { rootLogger } from "./lib/logger.js";
import { timmyIdentityService } from "./lib/timmy-identity.js";
import { startEngagementEngine } from "./lib/engagement.js";
import { relayAccountService } from "./lib/relay-accounts.js";
const rawPort = process.env["PORT"];
if (!rawPort) {
throw new Error("PORT environment variable is required but was not provided.");
}
const port = Number(rawPort);
if (Number.isNaN(port) || port <= 0) {
throw new Error(`Invalid PORT value: "${rawPort}"`);
}
const server = createServer(app);
attachWebSocketServer(server);
server.listen(port, () => {
rootLogger.info("server started", { port });
rootLogger.info("timmy identity", { npub: timmyIdentityService.npub });
const domain = process.env["REPLIT_DEV_DOMAIN"];
if (domain) {
rootLogger.info("public url", { url: `https://${domain}/api/ui` });
rootLogger.info("tower url", { url: `https://${domain}/tower` });
rootLogger.info("ws url", { url: `wss://${domain}/api/ws` });
}
startEngagementEngine();
// Seed Timmy's own pubkey with elite identity + relay write access.
// Resolves pubkey from TIMMY_NOSTR_PUBKEY env var if set (explicit override),
// otherwise falls back to the hex pubkey derived from TIMMY_NOSTR_NSEC.
// Idempotent — safe to run on every startup.
const timmyPubkey = process.env["TIMMY_NOSTR_PUBKEY"] ?? timmyIdentityService.pubkeyHex;
relayAccountService
.seedElite(timmyPubkey, "Timmy's own pubkey — elite access seeded at startup")
.then(() =>
rootLogger.info("relay: Timmy's pubkey seeded with elite access", {
pubkey: timmyPubkey.slice(0, 8),
source: process.env["TIMMY_NOSTR_PUBKEY"] ? "TIMMY_NOSTR_PUBKEY env" : "timmyIdentityService",
}),
)
.catch((err) =>
rootLogger.warn("relay: failed to seed Timmy's pubkey", { err }),
);
});