# Device Provisioning — Kiến trúc bảo mật ## Luồng provisioning ``` ┌─ Device Pi (DSLR agent) ──────────────────────┐ │ │ │ [1] BOOT │ │ - Generate UUID (machine-id hoặc random) │ │ - Generate claimCode = random 6-char │ │ - POST /v1/devices/claim │ │ { uuid, name, serialNo, claimCode } │ │ - Display claimCode + UUID on terminal │ │ - Poll /v1/devices/claim/:code/status │ │ │ │ [2] APPROVED? │ │ - /claim/:code/status → { status, apiKey }│ │ - Save apiKey to /boot/timelapse/agent.key│ │ - chmod 600 (owner read/write only) │ │ │ │ [3] HEARTBEAT LOOP │ │ - POST /v1/devices/:id/heartbeat │ │ X-API-Key: │ │ - gphoto2 capture loop │ │ - Poll /v1/devices/:id/commands │ └───────────────────────────────────────────────┘ HTTPS (TLS) HTTPS (TLS) ↓ ↑ ┌─ Server (NestJS) ────────────────────────────────────────────┐ │ │ │ POST /v1/devices/claim → claimCode + device info │ │ GET /v1/devices/claim/:code → polling status │ │ GET /v1/devices/pending → dashboard list │ │ POST /v1/devices/pending/:id/approve │ │ POST /v1/devices/pending/:id/reject │ │ POST /v1/devices/:id/heartbeat ← X-API-Key auth │ │ GET /v1/devices/:id/commands → pending commands queue │ └────────────────────────────────────────────────────────────┘ ``` ## Bảng `device_claims` (schema) ```sql CREATE TABLE device_claims ( id TEXT PRIMARY KEY, -- nanoid claim_code TEXT UNIQUE NOT NULL, -- 6-char, uppercase device_uuid TEXT UNIQUE NOT NULL, -- device unique id device_name TEXT NOT NULL, serial_no TEXT, status TEXT DEFAULT 'pending', -- pending | approved | rejected | expired api_key_hash TEXT, -- bcrypt hash of API key expires_at TIMESTAMP NOT NULL, -- 24h from creation approved_by TEXT, approved_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW() ); ``` ## Security Checklist - [ ] Claim code: 6-char alphanumeric, uppercase, expires 24h - [ ] API key: 32-char random, stored bcrypt-hashed on server - [ ] API key: stored in `/boot/timelapse/agent.key` (chmod 600) on device - [ ] TLS: server phải có valid HTTPS (production) - [ ] Rate limit: /claim endpoint — max 10 requests/IP/giờ - [ ] Device không lưu password — chỉ API key - [ ] Audit log: mọi provisioning event được ghi ## API Endpoints ### POST /v1/devices/claim Device gửi thông tin để đăng ký. **Request:** ```json { "deviceUuid": "pi-abc123", "deviceName": "Pi-Camera-01", "serialNo": "RPI-0001" } ``` **Response (201):** ```json { "claimCode": "XK7M2P", "status": "pending", "expiresAt": "2025-01-01T12:00:00Z" } ``` ### GET /v1/devices/claim/:code/status Device polling để kiểm tra approval. **Response:** ```json { "status": "pending" | "approved" | "rejected" | "expired", "apiKey": "..." // chỉ khi approved "deviceId": "DEMO-001" // chỉ khi approved } ``` ### GET /v1/devices/pending Dashboard list pending devices (admin only). ### POST /v1/devices/pending/:id/approve Admin approve → sinh API key → lưu hash → trả cho device qua polling. ### POST /v1/devices/pending/:id/reject Admin reject → xóa claim record.