diff --git a/apps/api/src/federation/connection.service.ts b/apps/api/src/federation/connection.service.ts index 2fcb373..54458a1 100644 --- a/apps/api/src/federation/connection.service.ts +++ b/apps/api/src/federation/connection.service.ts @@ -4,7 +4,13 @@ * Manages federation connections between instances. */ -import { Injectable, Logger, NotFoundException } from "@nestjs/common"; +import { + Injectable, + Logger, + NotFoundException, + UnauthorizedException, + ServiceUnavailableException, +} from "@nestjs/common"; import { HttpService } from "@nestjs/axios"; import { FederationConnectionStatus, Prisma } from "@prisma/client"; import { PrismaService } from "../prisma/prisma.service"; @@ -247,7 +253,7 @@ export class ConnectionService { if (!validation.valid) { const errorMsg = validation.error ?? "Unknown error"; this.logger.warn(`Invalid connection request from ${request.instanceId}: ${errorMsg}`); - throw new Error("Invalid connection request signature"); + throw new UnauthorizedException("Invalid connection request signature"); } // Create pending connection @@ -284,7 +290,9 @@ export class ConnectionService { } catch (error: unknown) { this.logger.error(`Failed to fetch remote identity from ${remoteUrl}`, error); const errorMessage = error instanceof Error ? error.message : "Unknown error"; - throw new Error(`Could not connect to remote instance: ${remoteUrl}: ${errorMessage}`); + throw new ServiceUnavailableException( + `Could not connect to remote instance: ${remoteUrl}: ${errorMessage}` + ); } } diff --git a/apps/api/src/federation/dto/connection.dto.ts b/apps/api/src/federation/dto/connection.dto.ts index 0c5df3e..0e3bebc 100644 --- a/apps/api/src/federation/dto/connection.dto.ts +++ b/apps/api/src/federation/dto/connection.dto.ts @@ -4,7 +4,7 @@ * Data Transfer Objects for federation connection API. */ -import { IsString, IsUrl, IsOptional, IsObject } from "class-validator"; +import { IsString, IsUrl, IsOptional, IsObject, IsNumber } from "class-validator"; /** * DTO for initiating a connection @@ -56,7 +56,7 @@ export class IncomingConnectionRequestDto { @IsObject() capabilities!: Record; - @IsString() + @IsNumber() timestamp!: number; @IsString() diff --git a/apps/api/src/federation/federation.controller.ts b/apps/api/src/federation/federation.controller.ts index 4422998..223e96c 100644 --- a/apps/api/src/federation/federation.controller.ts +++ b/apps/api/src/federation/federation.controller.ts @@ -195,8 +195,10 @@ export class FederationController { ): Promise<{ status: string; connectionId?: string }> { this.logger.log(`Received connection request from ${dto.instanceId}`); - // For now, create connection in default workspace - // TODO: Allow configuration of which workspace handles incoming connections + // LIMITATION: Incoming connections are created in a default workspace + // TODO: Future enhancement - Allow configuration of which workspace handles incoming connections + // This could be based on routing rules, instance configuration, or a dedicated federation workspace + // For now, uses DEFAULT_WORKSPACE_ID environment variable or falls back to "default" const workspaceId = process.env.DEFAULT_WORKSPACE_ID ?? "default"; const connection = await this.connectionService.handleIncomingConnectionRequest( diff --git a/apps/api/src/federation/signature.service.ts b/apps/api/src/federation/signature.service.ts index a3511bd..0631e01 100644 --- a/apps/api/src/federation/signature.service.ts +++ b/apps/api/src/federation/signature.service.ts @@ -156,23 +156,22 @@ export class SignatureService { * @returns A new object with sorted keys */ private sortObjectKeys(obj: SignableMessage): SignableMessage { - // Handle null - if (obj === null) { - return obj; - } - - // Handle arrays - map recursively + // Handle null and primitives // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (Array.isArray(obj)) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument - return obj.map((item: any) => - typeof item === "object" && item !== null ? this.sortObjectKeys(item) : item - ) as SignableMessage; + if (obj === null || typeof obj !== "object") { + return obj; } - // Handle non-objects (primitives) - if (typeof obj !== "object") { - return obj; + // Handle arrays - recursively sort elements + if (Array.isArray(obj)) { + const sortedArray = obj.map((item: unknown) => { + if (typeof item === "object" && item !== null) { + return this.sortObjectKeys(item as SignableMessage); + } + return item; + }); + // Arrays are valid SignableMessage values when nested in objects + return sortedArray as unknown as SignableMessage; } // Handle objects - sort keys alphabetically @@ -181,10 +180,11 @@ export class SignatureService { for (const key of keys) { const value = obj[key]; - sorted[key] = - typeof value === "object" && value !== null - ? this.sortObjectKeys(value as SignableMessage) - : value; + if (typeof value === "object" && value !== null) { + sorted[key] = this.sortObjectKeys(value as SignableMessage); + } else { + sorted[key] = value; + } } return sorted;