Added @UseGuards(AuthGuard) and rate limiting (@Throttle) to /api/v1/federation/identity/verify endpoint. Configured strict rate limit (10 req/min) to prevent abuse of this previously public endpoint. Added test to verify guards are applied. Security improvement: Prevents unauthorized access and rate limit abuse of identity verification endpoint. Fixes #290 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
156 lines
4.6 KiB
TypeScript
156 lines
4.6 KiB
TypeScript
/**
|
|
* 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 { Throttle } from "@nestjs/throttler";
|
|
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.
|
|
* Rate limit: "strict" tier (10 req/min) - public endpoint requiring authentication
|
|
*/
|
|
@Post("verify")
|
|
@UseGuards(AuthGuard)
|
|
@Throttle({ strict: { limit: 10, ttl: 60000 } })
|
|
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);
|
|
}
|
|
}
|