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:
ulhub
2026-07-18 01:40:12 +00:00
parent f1c94e9279
commit 842cb23e1f
57 changed files with 2283 additions and 67 deletions

View File

@@ -1,7 +1,9 @@
import { OrgRole } from '@prisma/client';
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsEnum, IsNotEmpty, IsString, MaxLength } from 'class-validator';
export class UpdateOrgDto {
@ApiProperty()
@IsString()
@IsNotEmpty()
@MaxLength(120)
@@ -9,14 +11,17 @@ export class UpdateOrgDto {
}
export class AddMemberDto {
@ApiProperty({ description: 'Must belong to an existing user (they must have registered already)' })
@IsEmail()
email: string;
@ApiProperty({ enum: OrgRole })
@IsEnum(OrgRole)
role: OrgRole;
}
export class UpdateMemberDto {
@ApiProperty({ enum: OrgRole })
@IsEnum(OrgRole)
role: OrgRole;
}

View File

@@ -1,4 +1,5 @@
import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { CurrentPrincipal } from '../auth/decorators/current-user.decorator';
import { Roles } from '../auth/decorators/roles.decorator';
import { JwtAuthGuard, UserOrApiKeyGuard } from '../auth/guards/auth.guard';
@@ -8,12 +9,14 @@ import { UserPrincipal } from '../auth/principal';
import { AddMemberDto, UpdateMemberDto, UpdateOrgDto } from './dto/orgs.dto';
import { OrgsService } from './orgs.service';
@ApiTags('orgs')
@Controller('orgs')
export class OrgsController {
constructor(private readonly orgsService: OrgsService) {}
@Get()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('jwt')
listMine(@CurrentPrincipal() principal: UserPrincipal) {
return this.orgsService.listForUser(principal.userId);
}
@@ -21,12 +24,15 @@ export class OrgsController {
@Patch(':orgId')
@UseGuards(JwtAuthGuard, OrgRolesGuard)
@Roles('ORG_ADMIN')
@ApiBearerAuth('jwt')
rename(@Param('orgId') orgId: string, @Body() dto: UpdateOrgDto) {
return this.orgsService.rename(orgId, dto.name);
}
@Get(':orgId/members')
@UseGuards(UserOrApiKeyGuard, OrgRolesGuard, ScopesGuard)
@ApiBearerAuth('jwt')
@ApiSecurity('apiKey')
listMembers(@Param('orgId') orgId: string) {
return this.orgsService.listMembers(orgId);
}
@@ -34,6 +40,7 @@ export class OrgsController {
@Post(':orgId/members')
@UseGuards(JwtAuthGuard, OrgRolesGuard)
@Roles('ORG_ADMIN')
@ApiBearerAuth('jwt')
addMember(@Param('orgId') orgId: string, @Body() dto: AddMemberDto) {
return this.orgsService.addMember(orgId, dto.email, dto.role);
}
@@ -41,6 +48,7 @@ export class OrgsController {
@Patch(':orgId/members/:userId')
@UseGuards(JwtAuthGuard, OrgRolesGuard)
@Roles('ORG_ADMIN')
@ApiBearerAuth('jwt')
updateMember(
@Param('orgId') orgId: string,
@Param('userId') userId: string,
@@ -52,6 +60,7 @@ export class OrgsController {
@Delete(':orgId/members/:userId')
@UseGuards(JwtAuthGuard, OrgRolesGuard)
@Roles('ORG_ADMIN')
@ApiBearerAuth('jwt')
removeMember(@Param('orgId') orgId: string, @Param('userId') userId: string) {
return this.orgsService.removeMember(orgId, userId);
}