fix(#85): resolve TypeScript compilation and validation issues
- Fix @IsNumber() validator on timestamp field (was @IsString() - critical security issue) - Fix TypeScript compilation error in sortObjectKeys array handling - Replace generic Error with UnauthorizedException and ServiceUnavailableException - Document hardcoded workspace ID limitation in handleIncomingConnection - Remove unused BadRequestException import All tests passing (70/70), TypeScript compiles cleanly, linting passes.
This commit is contained in:
@@ -4,7 +4,13 @@
|
|||||||
* Manages federation connections between instances.
|
* 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 { HttpService } from "@nestjs/axios";
|
||||||
import { FederationConnectionStatus, Prisma } from "@prisma/client";
|
import { FederationConnectionStatus, Prisma } from "@prisma/client";
|
||||||
import { PrismaService } from "../prisma/prisma.service";
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
@@ -247,7 +253,7 @@ export class ConnectionService {
|
|||||||
if (!validation.valid) {
|
if (!validation.valid) {
|
||||||
const errorMsg = validation.error ?? "Unknown error";
|
const errorMsg = validation.error ?? "Unknown error";
|
||||||
this.logger.warn(`Invalid connection request from ${request.instanceId}: ${errorMsg}`);
|
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
|
// Create pending connection
|
||||||
@@ -284,7 +290,9 @@ export class ConnectionService {
|
|||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
this.logger.error(`Failed to fetch remote identity from ${remoteUrl}`, error);
|
this.logger.error(`Failed to fetch remote identity from ${remoteUrl}`, error);
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown 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}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Data Transfer Objects for federation connection API.
|
* 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
|
* DTO for initiating a connection
|
||||||
@@ -56,7 +56,7 @@ export class IncomingConnectionRequestDto {
|
|||||||
@IsObject()
|
@IsObject()
|
||||||
capabilities!: Record<string, unknown>;
|
capabilities!: Record<string, unknown>;
|
||||||
|
|
||||||
@IsString()
|
@IsNumber()
|
||||||
timestamp!: number;
|
timestamp!: number;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -195,8 +195,10 @@ export class FederationController {
|
|||||||
): Promise<{ status: string; connectionId?: string }> {
|
): Promise<{ status: string; connectionId?: string }> {
|
||||||
this.logger.log(`Received connection request from ${dto.instanceId}`);
|
this.logger.log(`Received connection request from ${dto.instanceId}`);
|
||||||
|
|
||||||
// For now, create connection in default workspace
|
// LIMITATION: Incoming connections are created in a default workspace
|
||||||
// TODO: Allow configuration of which workspace handles incoming connections
|
// 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 workspaceId = process.env.DEFAULT_WORKSPACE_ID ?? "default";
|
||||||
|
|
||||||
const connection = await this.connectionService.handleIncomingConnectionRequest(
|
const connection = await this.connectionService.handleIncomingConnectionRequest(
|
||||||
|
|||||||
@@ -156,23 +156,22 @@ export class SignatureService {
|
|||||||
* @returns A new object with sorted keys
|
* @returns A new object with sorted keys
|
||||||
*/
|
*/
|
||||||
private sortObjectKeys(obj: SignableMessage): SignableMessage {
|
private sortObjectKeys(obj: SignableMessage): SignableMessage {
|
||||||
// Handle null
|
// Handle null and primitives
|
||||||
if (obj === null) {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle arrays - map recursively
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (Array.isArray(obj)) {
|
if (obj === null || typeof obj !== "object") {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument
|
return obj;
|
||||||
return obj.map((item: any) =>
|
|
||||||
typeof item === "object" && item !== null ? this.sortObjectKeys(item) : item
|
|
||||||
) as SignableMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle non-objects (primitives)
|
// Handle arrays - recursively sort elements
|
||||||
if (typeof obj !== "object") {
|
if (Array.isArray(obj)) {
|
||||||
return 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
|
// Handle objects - sort keys alphabetically
|
||||||
@@ -181,10 +180,11 @@ export class SignatureService {
|
|||||||
|
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const value = obj[key];
|
const value = obj[key];
|
||||||
sorted[key] =
|
if (typeof value === "object" && value !== null) {
|
||||||
typeof value === "object" && value !== null
|
sorted[key] = this.sortObjectKeys(value as SignableMessage);
|
||||||
? this.sortObjectKeys(value as SignableMessage)
|
} else {
|
||||||
: value;
|
sorted[key] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sorted;
|
return sorted;
|
||||||
|
|||||||
Reference in New Issue
Block a user