"""Multi-device provisioning helpers for the remote portal.""" import hashlib import re import secrets from . import db _DEVICE_NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$") def list_devices(): return db.list_devices() def validate_device_name(name): value = (name or "").strip() if not value: return None, "Device name is required." if not _DEVICE_NAME_RE.fullmatch(value): return None, "Device name must be 1-100 chars using letters, numbers, dot, underscore, or dash." return value, None def create_device(name): value, error = validate_device_name(name) if error: return None, {"name": error} if db.get_device_by_name(value) is not None: return None, {"name": "A device with that name already exists."} raw_key = secrets.token_urlsafe(32) device_id = db.create_device(value, _hash_key(raw_key)) return { "device": db.get_device_by_id(device_id), "api_key": raw_key, }, {} def rotate_device_key(device_id): device = db.get_device_by_id(device_id) if device is None: return None, {"error": "Device not found."} raw_key = secrets.token_urlsafe(32) db.rotate_device_api_key(device_id, _hash_key(raw_key)) return { "device": db.get_device_by_id(device_id), "api_key": raw_key, }, {} def request_sync(device_id): config_version = db.request_device_sync(device_id) if config_version is None: return None device = db.get_device_by_id(device_id) return { "device": device, "message": "Sync requested. The Pi will pick it up on its next online contact, or immediately if Sync now is used locally.", "config_version": config_version, } def delete_device(device_id, protected_device_name=None): device = db.get_device_by_id(device_id) if device is None: return False, {"error": "Device not found."} if protected_device_name and device["name"] == protected_device_name: return False, { "error": "Cannot delete the portal's default device while DEVICE_NAME still points to it.", } deleted_id = db.delete_device(device_id) if deleted_id is None: return False, {"error": "Device not found."} return True, {} def acknowledge_sync(device_id, config_version): db.acknowledge_device_sync(device_id, config_version) def _hash_key(raw_key): return hashlib.sha256(raw_key.encode("utf-8")).hexdigest()