Files
stack/apps/web/src/lib/api/federation.ts
Jason Woltje 1005b7969c
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix(SEC-WEB-37): Gate federation mock data behind NODE_ENV check
Replace exported const mockConnections with getMockConnections() function
that returns mock data only when NODE_ENV === "development". In production
and test environments, returns an empty array as defense-in-depth alongside
the existing ComingSoon page gate (SEC-WEB-4).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 18:22:12 -06:00

282 lines
7.0 KiB
TypeScript

/**
* Federation API Client
* Handles federation connection management API requests
*/
import { apiGet, apiPost, apiPatch } from "./client";
/**
* Federation connection status
*/
export enum FederationConnectionStatus {
PENDING = "PENDING",
ACTIVE = "ACTIVE",
DISCONNECTED = "DISCONNECTED",
REJECTED = "REJECTED",
}
/**
* Federation capabilities
*/
export interface FederationCapabilities {
supportsQuery: boolean;
supportsCommand: boolean;
supportsEvent: boolean;
supportsAgentSpawn: boolean;
protocolVersion: string;
}
/**
* Connection details
*/
export interface ConnectionDetails {
id: string;
workspaceId: string;
remoteInstanceId: string;
remoteUrl: string;
remotePublicKey: string;
remoteCapabilities: FederationCapabilities;
status: FederationConnectionStatus;
metadata: Record<string, unknown>;
createdAt: string;
updatedAt: string;
connectedAt: string | null;
disconnectedAt: string | null;
}
/**
* Public instance identity
*/
export interface PublicInstanceIdentity {
id: string;
instanceId: string;
name: string;
url: string;
publicKey: string;
capabilities: FederationCapabilities;
metadata: Record<string, unknown>;
createdAt: string;
updatedAt: string;
}
/**
* Initiate connection request
*/
export interface InitiateConnectionRequest {
remoteUrl: string;
}
/**
* Accept connection request
*/
export interface AcceptConnectionRequest {
metadata?: Record<string, unknown>;
}
/**
* Reject connection request
*/
export interface RejectConnectionRequest {
reason: string;
}
/**
* Disconnect connection request
*/
export interface DisconnectConnectionRequest {
reason?: string;
}
/**
* Fetch all connections
*/
export async function fetchConnections(
status?: FederationConnectionStatus
): Promise<ConnectionDetails[]> {
const params = new URLSearchParams();
if (status) {
params.append("status", status);
}
const queryString = params.toString();
const endpoint = queryString
? `/api/v1/federation/connections?${queryString}`
: "/api/v1/federation/connections";
return apiGet<ConnectionDetails[]>(endpoint);
}
/**
* Fetch a single connection
*/
export async function fetchConnection(connectionId: string): Promise<ConnectionDetails> {
return apiGet<ConnectionDetails>(`/api/v1/federation/connections/${connectionId}`);
}
/**
* Initiate a new connection
*/
export async function initiateConnection(
request: InitiateConnectionRequest
): Promise<ConnectionDetails> {
return apiPost<ConnectionDetails>("/api/v1/federation/connections/initiate", request);
}
/**
* Accept a pending connection
*/
export async function acceptConnection(
connectionId: string,
request?: AcceptConnectionRequest
): Promise<ConnectionDetails> {
return apiPost<ConnectionDetails>(
`/api/v1/federation/connections/${connectionId}/accept`,
request ?? {}
);
}
/**
* Reject a pending connection
*/
export async function rejectConnection(
connectionId: string,
request: RejectConnectionRequest
): Promise<ConnectionDetails> {
return apiPost<ConnectionDetails>(
`/api/v1/federation/connections/${connectionId}/reject`,
request
);
}
/**
* Disconnect an active connection
*/
export async function disconnectConnection(
connectionId: string,
request?: DisconnectConnectionRequest
): Promise<ConnectionDetails> {
return apiPost<ConnectionDetails>(
`/api/v1/federation/connections/${connectionId}/disconnect`,
request ?? {}
);
}
/**
* Get this instance's public identity
*/
export async function fetchInstanceIdentity(): Promise<PublicInstanceIdentity> {
return apiGet<PublicInstanceIdentity>("/api/v1/federation/instance");
}
/**
* Update instance configuration request
*/
export interface UpdateInstanceRequest {
name?: string;
capabilities?: FederationCapabilities;
metadata?: Record<string, unknown>;
}
/**
* Update this instance's configuration
* Admin-only operation
*/
export async function updateInstanceConfiguration(
updates: UpdateInstanceRequest
): Promise<PublicInstanceIdentity> {
return apiPatch<PublicInstanceIdentity>("/api/v1/federation/instance", updates);
}
/**
* Regenerate instance keypair
* Admin-only operation
*/
export async function regenerateInstanceKeys(): Promise<PublicInstanceIdentity> {
return apiPost<PublicInstanceIdentity>("/api/v1/federation/instance/regenerate-keys", {});
}
/**
* Get mock connections for development only.
* Returns an empty array in production as defense-in-depth.
* The federation pages are also gated behind a ComingSoon component
* in production (SEC-WEB-4), but this provides an additional layer.
*/
export function getMockConnections(): ConnectionDetails[] {
if (process.env.NODE_ENV !== "development") {
return [];
}
return [
{
id: "conn-1",
workspaceId: "workspace-1",
remoteInstanceId: "instance-work-001",
remoteUrl: "https://mosaic.work.example.com",
remotePublicKey: "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
remoteCapabilities: {
supportsQuery: true,
supportsCommand: true,
supportsEvent: true,
supportsAgentSpawn: true,
protocolVersion: "1.0",
},
status: FederationConnectionStatus.ACTIVE,
metadata: {
name: "Work Instance",
description: "Corporate Mosaic instance",
},
createdAt: new Date("2026-02-01").toISOString(),
updatedAt: new Date("2026-02-01").toISOString(),
connectedAt: new Date("2026-02-01").toISOString(),
disconnectedAt: null,
},
{
id: "conn-2",
workspaceId: "workspace-1",
remoteInstanceId: "instance-partner-001",
remoteUrl: "https://mosaic.partner.example.com",
remotePublicKey: "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
remoteCapabilities: {
supportsQuery: true,
supportsCommand: false,
supportsEvent: true,
supportsAgentSpawn: false,
protocolVersion: "1.0",
},
status: FederationConnectionStatus.PENDING,
metadata: {
name: "Partner Instance",
description: "Shared project collaboration",
},
createdAt: new Date("2026-02-02").toISOString(),
updatedAt: new Date("2026-02-02").toISOString(),
connectedAt: null,
disconnectedAt: null,
},
{
id: "conn-3",
workspaceId: "workspace-1",
remoteInstanceId: "instance-old-001",
remoteUrl: "https://mosaic.old.example.com",
remotePublicKey: "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
remoteCapabilities: {
supportsQuery: true,
supportsCommand: true,
supportsEvent: false,
supportsAgentSpawn: false,
protocolVersion: "1.0",
},
status: FederationConnectionStatus.DISCONNECTED,
metadata: {
name: "Previous Instance",
description: "No longer in use",
},
createdAt: new Date("2026-01-15").toISOString(),
updatedAt: new Date("2026-01-30").toISOString(),
connectedAt: new Date("2026-01-15").toISOString(),
disconnectedAt: new Date("2026-01-30").toISOString(),
},
];
}