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:
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "devices" ADD COLUMN "lastPosition" JSONB,
|
||||
ADD COLUMN "lastPositionAt" TIMESTAMPTZ(6);
|
||||
@@ -0,0 +1,20 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "device_certificates" (
|
||||
"id" TEXT NOT NULL,
|
||||
"deviceId" TEXT NOT NULL,
|
||||
"serialNumber" TEXT NOT NULL,
|
||||
"commonName" TEXT NOT NULL,
|
||||
"certificatePem" TEXT NOT NULL,
|
||||
"privateKeyPem" TEXT NOT NULL,
|
||||
"fingerprint" TEXT NOT NULL,
|
||||
"issuedAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expiresAt" TIMESTAMPTZ(6) NOT NULL,
|
||||
|
||||
CONSTRAINT "device_certificates_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "device_certificates_deviceId_key" ON "device_certificates"("deviceId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "device_certificates" ADD CONSTRAINT "device_certificates_deviceId_fkey" FOREIGN KEY ("deviceId") REFERENCES "devices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -142,15 +142,45 @@ model Device {
|
||||
// fetches this via GET /api/devices/:serial/status to show on its own screen.
|
||||
disabledReason String?
|
||||
lastSeenAt DateTime? @db.Timestamptz(6)
|
||||
// Most recent known position, from either a "status" ping (live-only, never
|
||||
// persisted as a LocatePoint) or a "log" point — whichever is newest. Lets
|
||||
// the devices page show current position without waiting for a log write.
|
||||
lastPosition Json?
|
||||
lastPositionAt DateTime? @db.Timestamptz(6)
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
points LocatePoint[]
|
||||
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
|
||||
points LocatePoint[]
|
||||
certificate DeviceCertificate?
|
||||
|
||||
@@map("devices")
|
||||
}
|
||||
|
||||
// A client certificate issued to a device for mTLS auth on the broker's 8883
|
||||
// listener; the cert's CN (= serialNumber) becomes the MQTT username. The CA
|
||||
// key/server key never touch the DB (see PkiService) — only device
|
||||
// certs/keys are stored here, mirroring how the reference implementation
|
||||
// (MQTT_DEVICE_AUTH.md) does it. One active cert per device; issuing a new
|
||||
// one requires deleting this row first. No revokedAt: deleting the row is
|
||||
// the only "revoke" action there is, and it isn't broker-enforced either way
|
||||
// (no CRL/OCSP), so a soft-delete flag would misleadingly imply otherwise.
|
||||
model DeviceCertificate {
|
||||
id String @id @default(cuid())
|
||||
deviceId String @unique
|
||||
serialNumber String
|
||||
commonName String
|
||||
certificatePem String @db.Text
|
||||
privateKeyPem String @db.Text
|
||||
fingerprint String
|
||||
issuedAt DateTime @default(now()) @db.Timestamptz(6)
|
||||
expiresAt DateTime @db.Timestamptz(6)
|
||||
|
||||
device Device @relation(fields: [deviceId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("device_certificates")
|
||||
}
|
||||
|
||||
model LocatePoint {
|
||||
id BigInt @id @default(autoincrement())
|
||||
jobId String
|
||||
|
||||
Reference in New Issue
Block a user