feat(#87): implement cross-instance identity linking for federation
Implements FED-004: Cross-Instance Identity Linking, building on the foundation from FED-001, FED-002, and FED-003. New Services: - IdentityLinkingService: Handles identity verification and mapping with signature validation and OIDC token verification - IdentityResolutionService: Resolves identities between local and remote instances with support for bulk operations New API Endpoints (IdentityLinkingController): - POST /api/v1/federation/identity/verify - Verify remote identity - POST /api/v1/federation/identity/resolve - Resolve remote to local user - POST /api/v1/federation/identity/bulk-resolve - Bulk resolution - GET /api/v1/federation/identity/me - Get current user's identities - POST /api/v1/federation/identity/link - Create identity mapping - PATCH /api/v1/federation/identity/:id - Update mapping - DELETE /api/v1/federation/identity/:id - Revoke mapping - GET /api/v1/federation/identity/:id/validate - Validate mapping Security Features: - Signature verification using remote instance public keys - OIDC token validation before creating mappings - Timestamp validation to prevent replay attacks - Workspace isolation via authentication guards - Comprehensive audit logging for all identity operations Enhancements: - Added SignatureService.verifyMessage() for remote signature verification - Added FederationService.getConnectionByRemoteInstanceId() - Extended FederationAuditService with identity logging methods - Created comprehensive DTOs with class-validator decorators Testing: - 38 new tests (19 service + 7 resolution + 12 controller) - All 132 federation tests passing - TypeScript compilation passing with no errors - High test coverage achieved (>85% requirement exceeded) Technical Details: - Leverages existing FederatedIdentity model from FED-003 - Uses RSA SHA-256 signatures for cryptographic verification - Supports one identity mapping per remote instance per user - Resolution service optimized for read-heavy operations - Built following TDD principles (Red-Green-Refactor) Closes #87 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
151
apps/api/src/federation/identity-linking.controller.ts
Normal file
151
apps/api/src/federation/identity-linking.controller.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Identity Linking Controller
|
||||
*
|
||||
* API endpoints for cross-instance identity verification and management.
|
||||
*/
|
||||
|
||||
import { Controller, Post, Get, Patch, Delete, Body, Param, UseGuards } from "@nestjs/common";
|
||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||
import { IdentityLinkingService } from "./identity-linking.service";
|
||||
import { IdentityResolutionService } from "./identity-resolution.service";
|
||||
import { CurrentUser } from "../auth/decorators/current-user.decorator";
|
||||
import type {
|
||||
VerifyIdentityDto,
|
||||
ResolveIdentityDto,
|
||||
BulkResolveIdentityDto,
|
||||
CreateIdentityMappingDto,
|
||||
UpdateIdentityMappingDto,
|
||||
} from "./dto/identity-linking.dto";
|
||||
import type {
|
||||
IdentityVerificationResponse,
|
||||
IdentityResolutionResponse,
|
||||
BulkIdentityResolutionResponse,
|
||||
IdentityMappingValidation,
|
||||
} from "./types/identity-linking.types";
|
||||
import type { FederatedIdentity } from "./types/oidc.types";
|
||||
|
||||
/**
|
||||
* User object from authentication
|
||||
*/
|
||||
interface AuthenticatedUser {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Controller("federation/identity")
|
||||
export class IdentityLinkingController {
|
||||
constructor(
|
||||
private readonly identityLinkingService: IdentityLinkingService,
|
||||
private readonly identityResolutionService: IdentityResolutionService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* POST /api/v1/federation/identity/verify
|
||||
*
|
||||
* Verify a user's identity from a remote instance.
|
||||
* Validates signature and OIDC token.
|
||||
*/
|
||||
@Post("verify")
|
||||
async verifyIdentity(@Body() dto: VerifyIdentityDto): Promise<IdentityVerificationResponse> {
|
||||
return this.identityLinkingService.verifyIdentity(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/federation/identity/resolve
|
||||
*
|
||||
* Resolve a remote user to a local user.
|
||||
*/
|
||||
@Post("resolve")
|
||||
@UseGuards(AuthGuard)
|
||||
async resolveIdentity(@Body() dto: ResolveIdentityDto): Promise<IdentityResolutionResponse> {
|
||||
return this.identityResolutionService.resolveIdentity(dto.remoteInstanceId, dto.remoteUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/federation/identity/bulk-resolve
|
||||
*
|
||||
* Bulk resolve multiple remote users to local users.
|
||||
*/
|
||||
@Post("bulk-resolve")
|
||||
@UseGuards(AuthGuard)
|
||||
async bulkResolveIdentity(
|
||||
@Body() dto: BulkResolveIdentityDto
|
||||
): Promise<BulkIdentityResolutionResponse> {
|
||||
return this.identityResolutionService.bulkResolveIdentities(
|
||||
dto.remoteInstanceId,
|
||||
dto.remoteUserIds
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/federation/identity/me
|
||||
*
|
||||
* Get the current user's federated identities.
|
||||
*/
|
||||
@Get("me")
|
||||
@UseGuards(AuthGuard)
|
||||
async getCurrentUserIdentities(
|
||||
@CurrentUser() user: AuthenticatedUser
|
||||
): Promise<FederatedIdentity[]> {
|
||||
return this.identityLinkingService.listUserIdentities(user.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/federation/identity/link
|
||||
*
|
||||
* Create a new identity mapping for the current user.
|
||||
*/
|
||||
@Post("link")
|
||||
@UseGuards(AuthGuard)
|
||||
async createIdentityMapping(
|
||||
@CurrentUser() user: AuthenticatedUser,
|
||||
@Body() dto: CreateIdentityMappingDto
|
||||
): Promise<FederatedIdentity> {
|
||||
return this.identityLinkingService.createIdentityMapping(user.id, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH /api/v1/federation/identity/:remoteInstanceId
|
||||
*
|
||||
* Update an existing identity mapping.
|
||||
*/
|
||||
@Patch(":remoteInstanceId")
|
||||
@UseGuards(AuthGuard)
|
||||
async updateIdentityMapping(
|
||||
@CurrentUser() user: AuthenticatedUser,
|
||||
@Param("remoteInstanceId") remoteInstanceId: string,
|
||||
@Body() dto: UpdateIdentityMappingDto
|
||||
): Promise<FederatedIdentity> {
|
||||
return this.identityLinkingService.updateIdentityMapping(user.id, remoteInstanceId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/v1/federation/identity/:remoteInstanceId
|
||||
*
|
||||
* Revoke an identity mapping.
|
||||
*/
|
||||
@Delete(":remoteInstanceId")
|
||||
@UseGuards(AuthGuard)
|
||||
async revokeIdentityMapping(
|
||||
@CurrentUser() user: AuthenticatedUser,
|
||||
@Param("remoteInstanceId") remoteInstanceId: string
|
||||
): Promise<{ success: boolean }> {
|
||||
await this.identityLinkingService.revokeIdentityMapping(user.id, remoteInstanceId);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/v1/federation/identity/:remoteInstanceId/validate
|
||||
*
|
||||
* Validate an identity mapping exists and is valid.
|
||||
*/
|
||||
@Get(":remoteInstanceId/validate")
|
||||
@UseGuards(AuthGuard)
|
||||
async validateIdentityMapping(
|
||||
@CurrentUser() user: AuthenticatedUser,
|
||||
@Param("remoteInstanceId") remoteInstanceId: string
|
||||
): Promise<IdentityMappingValidation> {
|
||||
return this.identityLinkingService.validateIdentityMapping(user.id, remoteInstanceId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user