Add device-certificate mTLS auth, live position tracking, and API docs
Introduces a CA/PKI module so field devices can authenticate to Mosquitto over TLS (8883) with per-device client certificates (CN = serial number) instead of a shared password, with matching Devices/MQTT-Certs UI. Adds live transmitter position tracking alongside logged points, an MQTTS transport option in the simulator for exercising the real cert-auth path, and Swagger API docs at /api/docs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
82
backend/src/certificates/certificates.controller.ts
Normal file
82
backend/src/certificates/certificates.controller.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Body, Controller, Delete, Get, Header, Param, Post, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard } from '../auth/guards/auth.guard';
|
||||
import { OrgRolesGuard } from '../auth/guards/org-roles.guard';
|
||||
import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { CertificatesService } from './certificates.service';
|
||||
import { ProvisionMqttCertDto } from './dto/certificates.dto';
|
||||
import { RequireAnyOrgAdminGuard } from './guards/require-any-org-admin.guard';
|
||||
|
||||
// Global broker/CA management — not org-nested, since the CA is broker-wide
|
||||
// (mirrors serialNumber already being globally unique across orgs). JWT-only,
|
||||
// same precedent as api-keys: credential-minting endpoints never accept an
|
||||
// API key.
|
||||
@ApiTags('certificates')
|
||||
@ApiBearerAuth('jwt')
|
||||
@Controller('certificates')
|
||||
@UseGuards(JwtAuthGuard, RequireAnyOrgAdminGuard)
|
||||
export class CertificatesController {
|
||||
constructor(private readonly certificatesService: CertificatesService) {}
|
||||
|
||||
@Get('ca')
|
||||
caStatus() {
|
||||
return this.certificatesService.getCaStatus();
|
||||
}
|
||||
|
||||
@Post('ca/init')
|
||||
initCa() {
|
||||
return this.certificatesService.initCa();
|
||||
}
|
||||
|
||||
@Post('mqtt/provision')
|
||||
provisionMqttCert(@Body() dto: ProvisionMqttCertDto) {
|
||||
return this.certificatesService.provisionMqttCert(dto);
|
||||
}
|
||||
|
||||
@Get('ca/download')
|
||||
@Header('Content-Type', 'application/x-pem-file')
|
||||
@Header('Content-Disposition', 'attachment; filename="ca.crt"')
|
||||
downloadCa() {
|
||||
return this.certificatesService.downloadCaCert();
|
||||
}
|
||||
}
|
||||
|
||||
// Per-device certificate issuance, nested under the org's devices like the
|
||||
// rest of the domain. Same guard stack as devices create/disable.
|
||||
@ApiTags('certificates')
|
||||
@ApiBearerAuth('jwt')
|
||||
@Controller('orgs/:orgId/devices/:deviceId/certificate')
|
||||
@UseGuards(JwtAuthGuard, OrgRolesGuard)
|
||||
@Roles('ORG_ADMIN')
|
||||
export class DeviceCertificatesController {
|
||||
constructor(private readonly certificatesService: CertificatesService) {}
|
||||
|
||||
@Post()
|
||||
issue(@Param('orgId') orgId: string, @Param('deviceId') deviceId: string) {
|
||||
return this.certificatesService.issueDeviceCertificate(orgId, deviceId);
|
||||
}
|
||||
|
||||
@Get()
|
||||
get(@Param('orgId') orgId: string, @Param('deviceId') deviceId: string) {
|
||||
return this.certificatesService.getDeviceCertificate(orgId, deviceId);
|
||||
}
|
||||
|
||||
@Get('cert')
|
||||
@Header('Content-Type', 'application/x-pem-file')
|
||||
@Header('Content-Disposition', 'attachment; filename="device.crt"')
|
||||
downloadCert(@Param('orgId') orgId: string, @Param('deviceId') deviceId: string) {
|
||||
return this.certificatesService.downloadDeviceCert(orgId, deviceId);
|
||||
}
|
||||
|
||||
@Get('key')
|
||||
@Header('Content-Type', 'application/x-pem-file')
|
||||
@Header('Content-Disposition', 'attachment; filename="device.key"')
|
||||
downloadKey(@Param('orgId') orgId: string, @Param('deviceId') deviceId: string) {
|
||||
return this.certificatesService.downloadDeviceKey(orgId, deviceId);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
revoke(@Param('orgId') orgId: string, @Param('deviceId') deviceId: string) {
|
||||
return this.certificatesService.revokeDeviceCertificate(orgId, deviceId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user