Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use cookie for authentication #167

Merged
merged 28 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1e62797
Use cookie for authentication
nicoinch Nov 17, 2022
caf7e9e
Merged changes from nicoinch
Cruguah Feb 19, 2023
ff77883
Merge branch 'nicoinch-main'
Cruguah Feb 19, 2023
1e3ecc7
Resolved some warnings
Cruguah Feb 22, 2023
c656727
Resolved a spelling error
Cruguah Feb 22, 2023
1d7eae2
Update pre-commit.yml
Cruguah Feb 22, 2023
8188f90
Update pre-commit.yml
Cruguah Feb 22, 2023
5a5f8a1
Update pre-commit.yml
Cruguah Feb 22, 2023
1219049
Update pre-commit.yml
Cruguah Feb 22, 2023
4c0603b
Resolve lint issues
Cruguah Feb 22, 2023
c9c29d5
Merge branch 'main' of https://github.com/Cruguah/ha-nest-protect
Cruguah Feb 22, 2023
087d19a
Update pre-commit.yml
Cruguah Feb 22, 2023
52a1f40
processed review comments
Cruguah Feb 25, 2023
ffd9bff
Merge branch 'main' of https://github.com/Cruguah/ha-nest-protect
Cruguah Feb 25, 2023
fbf9865
Resolved a validation issue
Cruguah Mar 8, 2023
3d83a84
Remove the version number
Cruguah Mar 12, 2023
7ecb075
Added the version tag again
Cruguah Mar 12, 2023
49657a7
Solved multiple issues
Cruguah Mar 27, 2023
eac9513
Resolve lint issues
Cruguah Mar 27, 2023
18e0214
Added reconfiguration to the nest integration
Cruguah Apr 5, 2023
e7ac358
Resolve pre-commit issue
Cruguah Apr 25, 2023
76ba32f
Try to solve the partial upgrade of isort config
Cruguah Apr 26, 2023
94f3a82
Try to resolve pre-check errors
Cruguah Apr 26, 2023
4cfcc16
Merge branch 'beta' into main
iMicknl Apr 26, 2023
ffe7a25
Resolve a conflict
Cruguah Apr 26, 2023
1e19d6e
Merge branch 'main' of https://github.com/Cruguah/ha-nest-protect
Cruguah Apr 26, 2023
9798b60
Revert changes
iMicknl Apr 26, 2023
7221d01
Update settings.json
iMicknl Apr 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
"*.yaml": "home-assistant"
},
"python.formatting.provider": "black"
}
}
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This integration will add the most important sensors of your Nest Protect device

- Only Google Accounts are supported, there is no plan to support legacy Nest accounts
- When Nest Protect (wired) occupancy is triggered, it will stay 'on' for 10 minutes. (API limitation)
- Only *cookie authentication* is supported as Google removed the API key authentication method. This means that you need to login to the Nest website at least once to generate a cookie. This cookie will be used to authenticate with the Nest API. The cookie will be stored in the Home Assistant configuration folder and will be used for future requests. If you logout from your browser or change your password, you need to reautenticate and and replace the current issue_token and cookies.

## Installation

Expand All @@ -33,6 +34,23 @@ Copy the `custom_components/nest_protect` to your custom_components folder. Rebo
[![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=nest_protect)


## Retrieving `issue_token` and `cookies`

(adapted from [homebridge-nest documentation](https://github.com/chrisjshull/homebridge-nest))

The values of "issue_token" and "cookies" are specific to your Google Account. To get them, follow these steps (only needs to be done once, as long as you stay logged into your Google Account).

1. Open a Chrome browser tab in Incognito Mode (or clear your cache).
2. Open Developer Tools (View/Developer/Developer Tools).
3. Click on **Network** tab. Make sure 'Preserve Log' is checked.
4. In the **Filter** box, enter *issueToken*
5. Go to home.nest.com, and click **Sign in with Google**. Log into your account.
6. One network call (beginning with iframerpc) will appear in the Dev Tools window. Click on it.
7. In the Headers tab, under General, copy the entire Request URL (beginning with https://accounts.google.com). This is your `issue_token` in the configuration form.
8. In the **Filter** box, enter *oauth2/iframe*.
9. Several network calls will appear in the Dev Tools window. Click on the last iframe call.
10. In the **Headers** tab, under **Request Headers**, copy the entire cookie (include the whole string which is several lines long and has many field/value pairs - do not include the cookie: name). This is your `cookies` in the configuration form.
11. Do not log out of home.nest.com, as this will invalidate your credentials. Just close the browser tab.

## Advanced

Expand Down
35 changes: 28 additions & 7 deletions custom_components/nest_protect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@
from homeassistant.helpers.device_registry import DeviceEntry
from homeassistant.helpers.dispatcher import async_dispatcher_send

from .const import CONF_ACCOUNT_TYPE, CONF_REFRESH_TOKEN, DOMAIN, LOGGER, PLATFORMS
from .const import (
CONF_ACCOUNT_TYPE,
CONF_COOKIES,
CONF_ISSUE_TOKEN,
CONF_REFRESH_TOKEN,
DOMAIN,
LOGGER,
PLATFORMS,
)
from .pynest.client import NestClient
from .pynest.const import NEST_ENVIRONMENTS
from .pynest.exceptions import (
Expand Down Expand Up @@ -52,13 +60,28 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Nest Protect from a config entry."""
refresh_token = entry.data[CONF_REFRESH_TOKEN]
issue_token = None
cookies = None
refresh_token = None

if CONF_ISSUE_TOKEN in entry.data and CONF_COOKIES in entry.data:
issue_token = entry.data[CONF_ISSUE_TOKEN]
cookies = entry.data[CONF_COOKIES]
if CONF_REFRESH_TOKEN in entry.data:
refresh_token = entry.data[CONF_REFRESH_TOKEN]
account_type = entry.data[CONF_ACCOUNT_TYPE]
session = async_get_clientsession(hass)
client = NestClient(session=session, environment=NEST_ENVIRONMENTS[account_type])

try:
auth = await client.get_access_token(refresh_token)
if issue_token and cookies:
auth = await client.get_access_token_from_cookies(issue_token, cookies)
elif refresh_token:
auth = await client.get_access_token_from_refresh_token(refresh_token)
else:
raise Exception(
"No cookies, issue token and refresh token, please provide issue_token and cookies or refresh_token"
)
nest = await client.authenticate(auth.access_token)
except (TimeoutError, ClientError) as exception:
raise ConfigEntryNotReady from exception
Expand Down Expand Up @@ -126,8 +149,6 @@ async def _async_subscribe_for_data(hass: HomeAssistant, entry: ConfigEntry, dat
"""Subscribe for new data."""
entry_data: HomeAssistantNestProtectData = hass.data[DOMAIN][entry.entry_id]

LOGGER.debug("Subscriber: listening for new data")

try:
# TODO move refresh token logic to client
if (
Expand All @@ -138,8 +159,8 @@ async def _async_subscribe_for_data(hass: HomeAssistant, entry: ConfigEntry, dat

if not entry_data.client.auth or entry_data.client.auth.is_expired():
LOGGER.debug("Subscriber: retrieving new Google access token")
await entry_data.client.get_access_token()
await entry_data.client.authenticate(entry_data.client.auth.access_token)
auth = await entry_data.client.get_access_token()
entry_data.client.nest_session = await entry_data.client.authenticate(auth)

# Subscribe to Google Nest subscribe endpoint
result = await entry_data.client.subscribe_for_data(
Expand Down
55 changes: 27 additions & 28 deletions custom_components/nest_protect/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,121 +38,121 @@ class NestProtectBinarySensorDescription(
key="co_status",
name="CO Status",
device_class=BinarySensorDeviceClass.CO,
value_fn=lambda state: state == 3,
value_fn=lambda state: state != 0,
Cruguah marked this conversation as resolved.
Show resolved Hide resolved
),
NestProtectBinarySensorDescription(
key="smoke_status",
name="Smoke Status",
device_class=BinarySensorDeviceClass.SMOKE,
value_fn=lambda state: state == 3,
value_fn=lambda state: state != 0,
),
NestProtectBinarySensorDescription(
key="heat_status",
name="Heat Status",
device_class=BinarySensorDeviceClass.HEAT,
value_fn=lambda state: state == 3,
value_fn=lambda state: state != 0,
),
NestProtectBinarySensorDescription(
key="component_speaker_test_passed",
name="Speaker Test",
value_fn=lambda state: not state,
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:speaker-wireless",
value_fn=lambda state: not state,
),
NestProtectBinarySensorDescription(
key="battery_health_state",
name="Battery Health",
value_fn=lambda state: state,
device_class=BinarySensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda state: state,
),
NestProtectBinarySensorDescription(
key="component_wifi_test_passed",
key="is_online",
name="Online",
value_fn=lambda state: state,
device_class=BinarySensorDeviceClass.CONNECTIVITY,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda state: state,
),
NestProtectBinarySensorDescription(
name="Smoke Test",
key="component_smoke_test_passed",
value_fn=lambda state: not state,
name="Smoke Test",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:smoke",
value_fn=lambda state: not state,
),
NestProtectBinarySensorDescription(
name="CO Test",
key="component_co_test_passed",
value_fn=lambda state: not state,
name="CO Test",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:molecule-co",
value_fn=lambda state: not state,
),
NestProtectBinarySensorDescription(
name="WiFi Test",
key="component_wifi_test_passed",
value_fn=lambda state: not state,
name="WiFi Test",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:wifi",
value_fn=lambda state: not state,
),
NestProtectBinarySensorDescription(
name="LED Test",
key="component_led_test_passed",
value_fn=lambda state: not state,
name="LED Test",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:led-off",
value_fn=lambda state: not state,
),
NestProtectBinarySensorDescription(
name="PIR Test",
key="component_pir_test_passed",
value_fn=lambda state: not state,
name="PIR Test",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:run",
value_fn=lambda state: not state,
),
NestProtectBinarySensorDescription(
name="Buzzer Test",
key="component_buzzer_test_passed",
value_fn=lambda state: not state,
name="Buzzer Test",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:alarm-bell",
value_fn=lambda state: not state,
),
# Disabled for now, since it seems like this state is not valid
# NestProtectBinarySensorDescription(
# name="Heat Test",
# key="component_heat_test_passed",
# value_fn=lambda state: not state,
# name="Heat Test",
# device_class=BinarySensorDeviceClass.PROBLEM,
# entity_category=EntityCategory.DIAGNOSTIC,
# icon="mdi:fire",
# value_fn=lambda state: not state
# ),
NestProtectBinarySensorDescription(
name="Humidity Test",
key="component_hum_test_passed",
value_fn=lambda state: not state,
name="Humidity Test",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:water-percent",
value_fn=lambda state: not state,
),
NestProtectBinarySensorDescription(
name="Occupancy",
key="auto_away",
value_fn=lambda state: not state,
name="Occupancy",
device_class=BinarySensorDeviceClass.OCCUPANCY,
wired_only=True,
value_fn=lambda state: not state,
),
NestProtectBinarySensorDescription(
name="Line Power",
key="line_power_present",
value_fn=lambda state: state,
name="Line Power",
device_class=BinarySensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
wired_only=True,
value_fn=lambda state: state,
),
]

Expand All @@ -170,7 +170,6 @@ async def async_setup_entry(hass, entry, async_add_devices):
for device in data.devices.values():
for key in device.value:
if description := SUPPORTED_KEYS.get(key):

# Not all entities are useful for battery powered Nest Protect devices
if description.wired_only and device.value["wired_or_battery"] != 0:
continue
Expand Down
Loading