registration.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. """
  2. Device registration endpoint.
  3. """
  4. import secrets
  5. from base64 import b64encode
  6. from datetime import datetime, timezone
  7. from typing import Annotated
  8. from fastapi import APIRouter, Depends, HTTPException, status
  9. from pydantic import BaseModel
  10. from sqlalchemy import select, update
  11. from sqlalchemy.ext.asyncio import AsyncSession
  12. from app.core.database import get_db
  13. from app.models.device import Device
  14. from app.models.settings import Settings
  15. router = APIRouter()
  16. class RegistrationRequest(BaseModel):
  17. """Device registration request."""
  18. device_id: str # MAC address
  19. ssh_public_key: str | None = None
  20. class RegistrationResponse(BaseModel):
  21. """Device registration response."""
  22. device_token: str
  23. device_password: str
  24. def _generate_token() -> str:
  25. """Generate 32-byte base64 token."""
  26. return b64encode(secrets.token_bytes(32)).decode("ascii")
  27. def _generate_password() -> str:
  28. """Generate 8-digit password."""
  29. n = secrets.randbelow(10**8)
  30. return f"{n:08d}"
  31. @router.post("/registration", response_model=RegistrationResponse, status_code=201)
  32. async def register_device(
  33. data: RegistrationRequest,
  34. db: Annotated[AsyncSession, Depends(get_db)],
  35. ):
  36. """
  37. Register new device or return existing credentials.
  38. Requires auto_registration to be enabled in settings.
  39. """
  40. mac_address = data.device_id.lower().strip()
  41. if not mac_address:
  42. raise HTTPException(
  43. status_code=status.HTTP_400_BAD_REQUEST,
  44. detail="Missing device_id",
  45. )
  46. # Check if device already exists
  47. result = await db.execute(select(Device).where(Device.mac_address == mac_address))
  48. device = result.scalar_one_or_none()
  49. if device:
  50. # Return existing credentials
  51. if not device.device_token or not device.device_password:
  52. # Re-generate if missing
  53. device.device_token = _generate_token()
  54. device.device_password = _generate_password()
  55. await db.commit()
  56. return RegistrationResponse(
  57. device_token=device.device_token,
  58. device_password=device.device_password,
  59. )
  60. # Check auto-registration setting
  61. settings_result = await db.execute(
  62. select(Settings).where(Settings.key == "auto_registration")
  63. )
  64. auto_reg_setting = settings_result.scalar_one_or_none()
  65. if not auto_reg_setting or not auto_reg_setting.value.get("enabled", False):
  66. raise HTTPException(
  67. status_code=status.HTTP_401_UNAUTHORIZED,
  68. detail="Registration disabled. Contact administrator.",
  69. )
  70. # Create new device
  71. device = Device(
  72. mac_address=mac_address,
  73. organization_id=None, # Unassigned
  74. status="online",
  75. config={"ssh_public_key": data.ssh_public_key} if data.ssh_public_key else {},
  76. device_token=_generate_token(),
  77. device_password=_generate_password(),
  78. )
  79. db.add(device)
  80. await db.flush()
  81. # Update last_device_at
  82. auto_reg_setting.value["last_device_at"] = datetime.now(timezone.utc).isoformat()
  83. await db.commit()
  84. await db.refresh(device)
  85. print(f"[REGISTRATION] device={mac_address} simple_id={device.simple_id}")
  86. return RegistrationResponse(
  87. device_token=device.device_token,
  88. device_password=device.device_password,
  89. )