"""Config flow for the html5 component.""" from __future__ import annotations import binascii from typing import Any, cast from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec from py_vapid import Vapid from py_vapid.utils import b64urlencode import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_NAME from homeassistant.core import callback from .const import ATTR_VAPID_EMAIL, ATTR_VAPID_PRV_KEY, ATTR_VAPID_PUB_KEY, DOMAIN from .issues import async_create_html5_issue def vapid_generate_private_key() -> str: """Generate a VAPID private key.""" private_key = ec.generate_private_key(ec.SECP256R1(), default_backend()) return b64urlencode( binascii.unhexlify(f"{private_key.private_numbers().private_value:x}".zfill(64)) ) def vapid_get_public_key(private_key: str) -> str: """Get the VAPID public key from a private key.""" vapid = Vapid.from_string(private_key) public_key = cast(ec.EllipticCurvePublicKey, vapid.public_key) return b64urlencode( public_key.public_bytes( serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint ) ) class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for HTML5.""" @callback def _async_create_html5_entry( self: HTML5ConfigFlow, data: dict[str, str] ) -> tuple[dict[str, str], ConfigFlowResult | None]: """Create an HTML5 entry.""" errors = {} flow_result = None if not data.get(ATTR_VAPID_PRV_KEY): data[ATTR_VAPID_PRV_KEY] = vapid_generate_private_key() # we will always generate the corresponding public key try: data[ATTR_VAPID_PUB_KEY] = vapid_get_public_key(data[ATTR_VAPID_PRV_KEY]) except (ValueError, binascii.Error): errors[ATTR_VAPID_PRV_KEY] = "invalid_prv_key" if not errors: config = { ATTR_VAPID_EMAIL: data[ATTR_VAPID_EMAIL], ATTR_VAPID_PRV_KEY: data[ATTR_VAPID_PRV_KEY], ATTR_VAPID_PUB_KEY: data[ATTR_VAPID_PUB_KEY], CONF_NAME: DOMAIN, } flow_result = self.async_create_entry(title="HTML5", data=config) return errors, flow_result async def async_step_user( self: HTML5ConfigFlow, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" errors: dict[str, str] = {} if user_input: errors, flow_result = self._async_create_html5_entry(user_input) if flow_result: return flow_result else: user_input = {} return self.async_show_form( data_schema=vol.Schema( { vol.Required( ATTR_VAPID_EMAIL, default=user_input.get(ATTR_VAPID_EMAIL, "") ): str, vol.Optional(ATTR_VAPID_PRV_KEY): str, } ), errors=errors, ) async def async_step_import( self: HTML5ConfigFlow, import_config: dict ) -> ConfigFlowResult: """Handle config import from yaml.""" _, flow_result = self._async_create_html5_entry(import_config) if not flow_result: async_create_html5_issue(self.hass, False) return self.async_abort(reason="invalid_config") async_create_html5_issue(self.hass, True) return flow_result