import { Controller, Post, Get, Delete, Body, Param, Query, UseGuards, Request, HttpStatus, HttpCode, BadRequestException, UseInterceptors, ClassSerializerInterceptor, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiParam, ApiQuery } from '@nestjs/swagger'; import { Throttle, ThrottlerGuard } from '@nestjs/throttler'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { DeviceRegistrationService } from '../services/device-registration.service'; import { InitiateRegistrationDto } from '../dto/initiate-registration.dto'; import { ClaimDeviceDto } from '../dto/claim-device.dto'; @ApiTags('device-registration') @Controller('api/v1/device-registration') @UseInterceptors(ClassSerializerInterceptor) export class DeviceRegistrationController { constructor( private readonly registrationService: DeviceRegistrationService, ) {} /** * Initiates device registration process */ @Post('initiate') @UseGuards(JwtAuthGuard, ThrottlerGuard) @Throttle({ default: { limit: 10, ttl: 3600 } }) // 10 requests per hour @ApiBearerAuth() @ApiOperation({ summary: 'Initiate device registration', description: 'Creates a new device registration session with QR code and PIN', }) @ApiResponse({ status: 201, description: 'Registration initiated successfully', schema: { type: 'object', properties: { claim_token: { type: 'string', description: 'Secure claim token' }, claim_id: { type: 'string', description: 'Human-readable claim ID' }, expires_in: { type: 'number', description: 'Token expiration in seconds' }, expires_at: { type: 'string', description: 'Token expiration timestamp' }, fallback_pin: { type: 'string', description: '6-digit fallback PIN' }, qr_code_url: { type: 'string', description: 'QR code data URL' }, websocket_url: { type: 'string', description: 'WebSocket URL for status updates' }, }, }, }) @ApiResponse({ status: 400, description: 'Bad request - validation error' }) @ApiResponse({ status: 401, description: 'Unauthorized' }) @ApiResponse({ status: 429, description: 'Too many requests' }) async initiateRegistration( @Body() dto: InitiateRegistrationDto, @Request() req: any, ) { // Extract userId from JWT payload const userId = req.user?.sub || req.user?.userId || req.user?.id; if (!userId) { throw new BadRequestException('User ID not found in JWT token'); } // Add request context to DTO dto.userAgent = req.headers['user-agent']; dto.ipAddress = req.ip || req.connection.remoteAddress; return this.registrationService.initiateRegistration(dto, userId); } /** * Claims a device using registration token */ @Post('claim') @UseGuards(ThrottlerGuard) @Throttle({ default: { limit: 20, ttl: 3600 } }) // 20 attempts per hour @ApiOperation({ summary: 'Claim device', description: 'Claims a device using hardware fingerprint and claim token', }) @ApiResponse({ status: 201, description: 'Device claimed successfully', schema: { type: 'object', properties: { challenge: { type: 'string', description: 'Security challenge to sign' }, algorithm: { type: 'string', description: 'Challenge signing algorithm' }, expires_at: { type: 'string', description: 'Challenge expiration time' }, }, }, }) @ApiResponse({ status: 400, description: 'Bad request - validation error' }) @ApiResponse({ status: 401, description: 'Invalid claim token' }) @ApiResponse({ status: 409, description: 'Device already registered' }) @ApiResponse({ status: 429, description: 'Too many requests' }) async claimDevice(@Body() dto: ClaimDeviceDto) { return this.registrationService.claimDevice(dto); } /** * Confirms device registration with challenge response */ @Post('confirm') @UseGuards(ThrottlerGuard) @Throttle({ default: { limit: 10, ttl: 3600 } }) // 10 attempts per hour @ApiOperation({ summary: 'Confirm device registration', description: 'Completes registration by validating challenge response', }) @ApiResponse({ status: 201, description: 'Registration completed successfully', schema: { type: 'object', properties: { device_id: { type: 'string', description: 'Unique device identifier' }, device_token: { type: 'string', description: 'JWT token for device authentication' }, device_certificate: { type: 'string', description: 'X.509 device certificate (PEM)' }, private_key: { type: 'string', description: 'Private key (PEM)' }, ca_certificate: { type: 'string', description: 'CA certificate (PEM)' }, api_endpoints: { type: 'object', description: 'API endpoint URLs for device', properties: { events: { type: 'string' }, telemetry: { type: 'string' }, config: { type: 'string' }, heartbeat: { type: 'string' }, commands: { type: 'string' }, }, }, initial_config: { type: 'object', description: 'Initial device configuration' }, registration_complete: { type: 'boolean', description: 'Registration status' }, }, }, }) @ApiResponse({ status: 400, description: 'Bad request - invalid challenge response' }) @ApiResponse({ status: 401, description: 'Challenge validation failed' }) @ApiResponse({ status: 429, description: 'Too many requests' }) async confirmRegistration( @Body() body: { claim_token: string; challenge_response: string }, ) { if (!body.claim_token || !body.challenge_response) { throw new BadRequestException('claim_token and challenge_response are required'); } return this.registrationService.confirmRegistration( body.claim_token, body.challenge_response, ); } /** * Gets registration status by claim ID */ @Get('status/:claim_id') @UseGuards(ThrottlerGuard) @Throttle({ default: { limit: 60, ttl: 3600 } }) // 60 requests per hour @ApiParam({ name: 'claim_id', description: 'Registration claim ID' }) @ApiOperation({ summary: 'Get registration status', description: 'Retrieves current status of device registration', }) @ApiResponse({ status: 200, description: 'Registration status retrieved successfully', schema: { type: 'object', properties: { status: { type: 'string', enum: ['pending', 'scanning', 'claiming', 'success', 'expired', 'failed'] }, device_id: { type: 'string', description: 'Device ID if registration completed' }, device_name: { type: 'string', description: 'Device name if available' }, progress: { type: 'number', description: 'Progress percentage (0-100)' }, error: { type: 'string', description: 'Error message if registration failed' }, expires_at: { type: 'string', description: 'Registration expiration time' }, }, }, }) @ApiResponse({ status: 404, description: 'Registration not found' }) @ApiResponse({ status: 429, description: 'Too many requests' }) async getRegistrationStatus(@Param('claim_id') claimId: string) { return this.registrationService.getRegistrationStatus(claimId); } /** * Cancels a pending registration */ @Delete(':claim_id') @UseGuards(JwtAuthGuard, ThrottlerGuard) @Throttle({ default: { limit: 20, ttl: 3600 } }) // 20 requests per hour @HttpCode(HttpStatus.NO_CONTENT) @ApiBearerAuth() @ApiParam({ name: 'claim_id', description: 'Registration claim ID to cancel' }) @ApiOperation({ summary: 'Cancel registration', description: 'Cancels a pending device registration', }) @ApiResponse({ status: 204, description: 'Registration cancelled successfully' }) @ApiResponse({ status: 400, description: 'Cannot cancel completed registration' }) @ApiResponse({ status: 401, description: 'Unauthorized' }) @ApiResponse({ status: 404, description: 'Registration not found' }) @ApiResponse({ status: 429, description: 'Too many requests' }) async cancelRegistration( @Param('claim_id') claimId: string, @Request() req: any, ) { const userId = req.user.sub; await this.registrationService.cancelRegistration(claimId, userId); } /** * Lists user's device registrations */ @Get('registrations') @UseGuards(JwtAuthGuard, ThrottlerGuard) @Throttle({ default: { limit: 30, ttl: 3600 } }) // 30 requests per hour @ApiBearerAuth() @ApiQuery({ name: 'status', required: false, description: 'Filter by registration status' }) @ApiQuery({ name: 'limit', required: false, description: 'Number of results to return (default: 20, max: 100)' }) @ApiQuery({ name: 'offset', required: false, description: 'Number of results to skip (default: 0)' }) @ApiOperation({ summary: 'List user registrations', description: 'Lists all device registrations for the authenticated user', }) @ApiResponse({ status: 200, description: 'User registrations retrieved successfully', schema: { type: 'object', properties: { registrations: { type: 'array', items: { type: 'object', properties: { claim_id: { type: 'string' }, status: { type: 'string' }, device_type: { type: 'string' }, created_at: { type: 'string' }, expires_at: { type: 'string' }, device_id: { type: 'string' }, device_name: { type: 'string' }, progress: { type: 'number' }, error: { type: 'string' }, }, }, }, total: { type: 'number', description: 'Total number of registrations' }, limit: { type: 'number', description: 'Results limit used' }, offset: { type: 'number', description: 'Results offset used' }, }, }, }) @ApiResponse({ status: 401, description: 'Unauthorized' }) @ApiResponse({ status: 429, description: 'Too many requests' }) async getUserRegistrations( @Request() req: any, @Query('status') status?: string, @Query('limit') limit: string = '20', @Query('offset') offset: string = '0', ) { const userId = req.user.sub; const limitNum = Math.min(parseInt(limit) || 20, 100); const offsetNum = parseInt(offset) || 0; // This would require implementing the method in the service // For now, return a placeholder response return { registrations: [], total: 0, limit: limitNum, offset: offsetNum, }; } /** * Test endpoint for device registration (development only) */ @Post('test-initiate') @UseGuards(ThrottlerGuard) @Throttle({ default: { limit: 10, ttl: 3600 } }) @ApiOperation({ summary: 'Test device registration initiation (dev only)', description: 'Creates a test registration session without authentication', }) async testInitiateRegistration( @Body() dto: InitiateRegistrationDto, @Request() req: any, ) { // Use a test user ID for development const testUserId = process.env.TEST_USER_ID || 'a8073bb8-e87e-4dbd-b9f2-3d0b8e8a1b47'; // Add request context to DTO dto.userAgent = req.headers['user-agent']; dto.ipAddress = req.ip || req.connection.remoteAddress; return this.registrationService.initiateRegistration(dto, testUserId); } /** * Gets registration statistics for admin users */ @Get('registration-stats') @UseGuards(JwtAuthGuard, ThrottlerGuard) @Throttle({ default: { limit: 10, ttl: 3600 } }) // 10 requests per hour @ApiBearerAuth() @ApiQuery({ name: 'period', required: false, description: 'Time period: 24h, 7d, 30d (default: 24h)' }) @ApiOperation({ summary: 'Get registration statistics', description: 'Retrieves device registration statistics (admin only)', }) @ApiResponse({ status: 200, description: 'Registration statistics retrieved successfully', schema: { type: 'object', properties: { total_registrations: { type: 'number' }, successful_registrations: { type: 'number' }, failed_registrations: { type: 'number' }, success_rate: { type: 'number' }, average_registration_time: { type: 'number' }, registrations_by_status: { type: 'object', additionalProperties: { type: 'number' }, }, registrations_over_time: { type: 'array', items: { type: 'object', properties: { date: { type: 'string' }, count: { type: 'number' }, }, }, }, }, }, }) @ApiResponse({ status: 401, description: 'Unauthorized' }) @ApiResponse({ status: 403, description: 'Forbidden - admin access required' }) @ApiResponse({ status: 429, description: 'Too many requests' }) async getRegistrationStats( @Request() req: any, @Query('period') period: string = '24h', ) { // TODO: Implement admin role check // TODO: Implement statistics calculation in service return { total_registrations: 0, successful_registrations: 0, failed_registrations: 0, success_rate: 0, average_registration_time: 0, registrations_by_status: {}, registrations_over_time: [], }; } }