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

Check cookies function #282

Open
wants to merge 7 commits into
base: beta
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 24 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +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.
- 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 @@ -26,31 +26,42 @@ Search for the Nest Protect integration and choose install. Reboot Home Assistan

[![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)


### Manual

Copy the `custom_components/nest_protect` to your custom_components folder. Reboot Home Assistant and configure the Nest Protect integration via the integrations page or press the blue button below.

[![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.
1. Open a Chrome/Edge browser tab in Incognito Mode (or clear your cookies).
- For **Incognito mode** (_recommended_), you first have to enable third-party cookies or the login will loop
- Goto Third-party cookie settings: Settings ➤ Privacy and security ➤ Third-party cookies (chrome://settings/cookies)
- Either:
- Add `home.nest.com` and `accounts.google.com` to "Allowed to use third-party cookies" customized list (_recommended_)
- Allow third-party cookies<br>
(default is: Block third-party cookies in Incognito mode)
- For security reasons, revert this change after you complete the process.
- If you already had open Incognito tabs, close all first and then open a new fresh one.
- If using a **normal browser tab**, make sure to clear your cookies
- Goto Privacy settings: Settings ➤ Privacy and security ➤ Clear browsing data (chrome://settings/clearBrowserData)
- In Time range select "All time" and make sure "Cookies and other site data is checked"
- Click Clear data
2. In the tab you want to use, open Developer Tools: More tools ➤ Developer tools (F12 or Ctrl-Shift-I).
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 request (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<span>://</span>accounts.google.com_). This is your _'issue_token'_ in the configuration form.
8. In the **Filter** box, enter `oauth2/iframe`.
9. Several requests will appear in the Dev Tools window. Click on the last iframe request.
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.
12. Remember to revert changes to third-party cookie settings.

## Advanced

Expand Down
45 changes: 43 additions & 2 deletions custom_components/nest_protect/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self) -> None:

self._config_entry = None
self._default_account_type = "production"
self._cookies_latest = ""

async def async_validate_input(self, user_input: dict[str, Any]) -> list:
"""Validate user credentials."""
Expand Down Expand Up @@ -95,6 +96,8 @@ async def async_step_account_link(
) -> FlowResult:
"""Handle a flow initialized by the user."""
errors = {}
issue_token = ""
cookies = ""

if user_input:
try:
Expand All @@ -104,10 +107,15 @@ async def async_step_account_link(
)
user_input[CONF_ISSUE_TOKEN] = issue_token
user_input[CONF_COOKIES] = cookies

self.check_cookies(cookies)

except (TimeoutError, ClientError):
errors["base"] = "cannot_connect"
except BadCredentialsException:
errors["base"] = "invalid_auth"
except ExcessCookiesException:
errors["base"] = "cookies_unknown"
except Exception as exception: # pylint: disable=broad-except
errors["base"] = "unknown"
LOGGER.exception(exception)
Expand Down Expand Up @@ -142,8 +150,8 @@ async def async_step_account_link(
step_id="account_link",
data_schema=vol.Schema(
{
vol.Required(CONF_ISSUE_TOKEN): str,
vol.Required(CONF_COOKIES): str,
vol.Required(CONF_ISSUE_TOKEN, default=issue_token): str,
vol.Required(CONF_COOKIES, default=cookies): str,
}
),
errors=errors,
Expand All @@ -162,3 +170,36 @@ async def async_step_reauth(
self._default_account_type = self._config_entry.data[CONF_ACCOUNT_TYPE]

return await self.async_step_account_link(user_input)

def check_cookies(self, cookies):
"""Check for other cookies than five expected ones."""
# If there are other cookies, it may be an indication of cache not being cleared properly.

if cookies != self._cookies_latest:
self._cookies_latest = cookies

cookie_dict = {
k.strip(): v.strip()
for (k, v) in (item.split("=", 1) for item in cookies.split(";"))
}

for cookie_name in cookie_dict.keys():
if cookie_name.upper() not in (
cn.upper()
for cn in [
"NID",
"__Secure-3PSID",
"__Secure-3PAPISID",
"__Host-3PLSID",
"__Secure-3PSIDCC",
]
):
raise ExcessCookiesException

self._cookies_latest = ""


class ExcessCookiesException(Exception):
"""Raised when there are more then the expected cookies."""

pass
2 changes: 2 additions & 0 deletions custom_components/nest_protect/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"config": {
"step": {
"user": {
"description": "Choose which environment to use. Unless instructed otherwise use the first and default option, 'Google Account'.",
"data": {
"account_type": "Account Type"
}
Expand All @@ -17,6 +18,7 @@
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"cookies_unknown": "[%key:common::config_flow::error::cookies_unknown%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
Expand Down
2 changes: 2 additions & 0 deletions custom_components/nest_protect/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"error": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"cookies_unknown": "Authentication succeeded, but it seems as if the cache may not have been properly cleared which could lead to reduced session lifetime. We recommend to retrieve cookies using Incognito Mode. Alternatively you may continue as is by clicking send once again.",
"unknown": "Unexpected error"
},
"step": {
"user": {
"description": "Choose which environment to use. Unless instructed otherwise use the first and default option, 'Google Account'.",
"data": {
"account_type": "Account Type"
}
Expand Down