feat(#84): implement instance identity model for federation

Implemented the foundation of federation architecture with instance
identity and connection management:

Database Schema:
- Added Instance model for instance identity with keypair generation
- Added FederationConnection model for workspace-scoped connections
- Added FederationConnectionStatus enum (PENDING, ACTIVE, SUSPENDED, DISCONNECTED)

Service Layer:
- FederationService with instance identity management
- RSA 2048-bit keypair generation for signing
- Public identity endpoint (excludes private key)
- Keypair regeneration capability

API Endpoints:
- GET /api/v1/federation/instance - Returns public instance identity
- POST /api/v1/federation/instance/regenerate-keys - Admin keypair regeneration

Tests:
- 11 tests passing (7 service, 4 controller)
- 100% statement coverage, 100% function coverage
- Follows TDD principles (Red-Green-Refactor)

Configuration:
- Added INSTANCE_NAME and INSTANCE_URL environment variables
- Integrated FederationModule into AppModule

Refs #84

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-03 10:58:50 -06:00
parent 6e63508f97
commit 7989c089ef
10 changed files with 853 additions and 22 deletions

View File

@@ -0,0 +1,157 @@
/**
* Federation Service
*
* Manages instance identity and federation connections.
*/
import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Instance, Prisma } from "@prisma/client";
import { generateKeyPairSync } from "crypto";
import { randomUUID } from "crypto";
import { PrismaService } from "../prisma/prisma.service";
import {
InstanceIdentity,
PublicInstanceIdentity,
KeyPair,
FederationCapabilities,
} from "./types/instance.types";
@Injectable()
export class FederationService {
private readonly logger = new Logger(FederationService.name);
constructor(
private readonly prisma: PrismaService,
private readonly config: ConfigService
) {}
/**
* Get the instance identity, creating it if it doesn't exist
*/
async getInstanceIdentity(): Promise<InstanceIdentity> {
// Try to find existing instance
let instance = await this.prisma.instance.findFirst();
if (!instance) {
this.logger.log("No instance identity found, creating new one");
instance = await this.createInstanceIdentity();
}
return this.mapToInstanceIdentity(instance);
}
/**
* Get public instance identity (without private key)
*/
async getPublicIdentity(): Promise<PublicInstanceIdentity> {
const instance = await this.getInstanceIdentity();
// Exclude private key from public identity
const { privateKey: _privateKey, ...publicIdentity } = instance;
return publicIdentity;
}
/**
* Generate a new RSA key pair for instance signing
*/
generateKeypair(): KeyPair {
const { publicKey, privateKey } = generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
},
});
return {
publicKey,
privateKey,
};
}
/**
* Regenerate the instance's keypair
*/
async regenerateKeypair(): Promise<InstanceIdentity> {
const instance = await this.getInstanceIdentity();
const { publicKey, privateKey } = this.generateKeypair();
const updatedInstance = await this.prisma.instance.update({
where: { id: instance.id },
data: {
publicKey,
privateKey,
},
});
this.logger.log("Instance keypair regenerated");
return this.mapToInstanceIdentity(updatedInstance);
}
/**
* Create a new instance identity
*/
private async createInstanceIdentity(): Promise<Instance> {
const { publicKey, privateKey } = this.generateKeypair();
const instanceId = this.generateInstanceId();
const name = this.config.get<string>("INSTANCE_NAME") ?? "Mosaic Instance";
const url = this.config.get<string>("INSTANCE_URL") ?? "http://localhost:3000";
const capabilities: FederationCapabilities = {
supportsQuery: true,
supportsCommand: true,
supportsEvent: true,
supportsAgentSpawn: true,
protocolVersion: "1.0",
};
const instance = await this.prisma.instance.create({
data: {
instanceId,
name,
url,
publicKey,
privateKey,
capabilities: capabilities as Prisma.JsonObject,
metadata: {},
},
});
this.logger.log(`Created instance identity: ${instanceId}`);
return instance;
}
/**
* Generate a unique instance ID
*/
private generateInstanceId(): string {
return `instance-${randomUUID()}`;
}
/**
* Map Prisma Instance to InstanceIdentity type
*/
private mapToInstanceIdentity(instance: Instance): InstanceIdentity {
return {
id: instance.id,
instanceId: instance.instanceId,
name: instance.name,
url: instance.url,
publicKey: instance.publicKey,
privateKey: instance.privateKey,
capabilities: instance.capabilities as FederationCapabilities,
metadata: instance.metadata as Record<string, unknown>,
createdAt: instance.createdAt,
updatedAt: instance.updatedAt,
};
}
}