Skip to content

Commit a5d3e04

Browse files
feat(auth)!: Implement new OAuth device code grant flow (#478)
* feat(auth)!: Implement new OAuth device code grant flow This commit introduces the new device code grant flow introduced by Tado and required as of 2025-03-21. The changes are highly based on python-tado, release 0.18.9 See: https://github.com/wmalgadey/PyTado/commits/0.18.9/ For more information about the change by Tado, See: https://support.tado.com/en/articles/8565472-how-do-i-authenticate-to-access-the-rest-api BREAKING CHANGE: Tado does not support password grant flow as authentication flow anymore. * chore: uncomment useful call in example * fix(auth): Create token file when absent to avoir errors * chore: Update .env.template file * fix(cli): Manage mutually exclusive cli arguments * ci: Remove matric strategy on python versions * ci: Remove matric strategy on python versions * ci: Remove matric strategy on python versions * fix(cli): Manage mutually exclusive cli arguments * chore(lint): Ruff linter * chore(lint): Yaml linter * docs: Add page to explain what are why about new auth flow * docs: Update tado generated jsonschemas * docs: Update readme with warning banner * ci: Disable unit tests job --------- Co-authored-by: Arjan Vlek <[email protected]>
1 parent ea4ff05 commit a5d3e04

24 files changed

+435
-255
lines changed

.env.template

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
TADO_USERNAME=""
2-
TADO_PASSWORD=""
3-
TADO_CLIENT_SECRET="wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc"
1+
TADO_BRIDGE_AUTHKEY="Cg3Z"
2+
TADO_CREDENTIALS_FILE="/tmp/.libtado_refresh_token.json"
3+
# TADO_REFRESH_TOKEN="BIv81abc7wH-K1mnhr1KabcE4OigjabcRX29lkSVQabcIwbDh6Wabc6wqRTgU5d4"

.github/workflows/release-feat-dryrun.yml

+8-8
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ jobs:
9696
poetry publish -r test-pypi
9797
if: needs.release.outputs.new_release_published == 'true'
9898

99-
# - name: Clean PyPi release
100-
# if: always()
101-
# run: |
102-
# poetry add pypi-cleanup
103-
# poetry run pypi-cleanup --host https://test.pypi.org --username $PYPI_CLEANUP_USERNAME --package libtado --do-it
104-
# env:
105-
# PYPI_CLEANUP_USERNAME: ${{ secrets.PYPI_TEST_USERNAME }}
106-
# PYPI_CLEANUP_PASSWORD: ${{ secrets.PYPI_TEST_TOKEN }}
99+
# - name: Clean PyPi release
100+
# if: always()
101+
# run: |
102+
# poetry add pypi-cleanup
103+
# poetry run pypi-cleanup --host https://test.pypi.org --username $PYPI_CLEANUP_USERNAME --package libtado --do-it
104+
# env:
105+
# PYPI_CLEANUP_USERNAME: ${{ secrets.PYPI_TEST_USERNAME }}
106+
# PYPI_CLEANUP_PASSWORD: ${{ secrets.PYPI_TEST_TOKEN }}

.github/workflows/template_test.yml

+7-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ jobs:
66
runs-on: ubuntu-latest
77
strategy:
88
matrix:
9-
python-version: ["3.8", "3.9", "3.10", "3.11"]
9+
python-version: ["3.11"]
10+
# python-version: ["3.8", "3.9", "3.10", "3.11"]
1011
poetry-version: ["main"]
1112

1213
steps:
@@ -29,7 +30,9 @@ jobs:
2930
export TADO_PASSWORD=$(echo $TADO_PASSWORD_B64 | base64 -d)
3031
poetry run pytest tests/api/test_api.py tests/api/test_tado.py
3132
env:
32-
TADO_USERNAME: ${{ secrets.TADO_USERNAME }}
33-
TADO_PASSWORD_B64: ${{ secrets.TADO_PASSWORD_B64 }}
34-
TADO_CLIENT_SECRET: ${{ secrets.TADO_CLIENT_SECRET }}
33+
# TADO_USERNAME: ${{ secrets.TADO_USERNAME }}
34+
# TADO_PASSWORD_B64: ${{ secrets.TADO_PASSWORD_B64 }}
35+
# TADO_CLIENT_SECRET: ${{ secrets.TADO_CLIENT_SECRET }}
3536
TADO_BRIDGE_AUTHKEY: ${{ secrets.TADO_BRIDGE_AUTHKEY }}
37+
# TADO_REFRESH_TOKEN: ${{ secrets.TADO_REFRESH_TOKEN }}
38+
TADO_CREDENTIALS_FILE: ${{ vars.TADO_CREDENTIALS_FILE }}

.github/workflows/test.yml

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
---
2-
name: Test
3-
on:
4-
- pull_request
1+
# ---
2+
# name: Test
3+
# on:
4+
# - pull_request
55

6-
jobs:
7-
pytest:
8-
uses: ./.github/workflows/template_test.yml
9-
secrets: inherit
6+
# jobs:
7+
# pytest:
8+
# uses: ./.github/workflows/template_test.yml
9+
# secrets: inherit

.pre-commit-config.yaml

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ repos:
3737
- id: poetry-lock
3838
- id: poetry-check
3939

40-
# - repo: https://github.com/python-poetry/poetry-plugin-export
41-
# rev: 1.9.0
42-
# hooks:
43-
# - id: poetry-export
40+
# - repo: https://github.com/python-poetry/poetry-plugin-export
41+
# rev: 1.9.0
42+
# hooks:
43+
# - id: poetry-export
4444

4545
- repo: local
4646
hooks:

README.md

+50-29
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ A library to control your Tado Smart Thermostat. This repository contains an act
1414

1515
**The tested version of APIs is Tado v2.**
1616

17+
## ⚠️ Breaking change in v4 ⚠️
18+
19+
Starting the **21st of March 2025**, the Tado authentication workflow will definitely change to OAuth2 device code grant flow.
20+
21+
Here is the link to the official announcement: [Tado Support Article - How do I authenticate to access the REST API?](https://support.tado.com/en/articles/8565472-how-do-i-authenticate-to-access-the-rest-api)
22+
23+
Now, you have to use the `TADO_CREDENTIALS_FILE` or `TADO_REFRESH` variables to authenticate.
24+
You can find more documentation on how to authenticate in the [**Libtado - CLI Configuration**](https://libtado.readthedocs.io/en/latest/cli/configuration/) documentation.
25+
1726
## Installation
1827

1928
You can download official library with `pip install libtado`.
@@ -26,53 +35,65 @@ git clone https://github.com/germainlefebvre4/libtado.git
2635

2736
Please check out [https://libtado.readthedocs.io](https://libtado.readthedocs.io) for more documentation.
2837

29-
## Preparation
38+
## Usage
3039

31-
Retrieve the `CLIENT_SECRET` before running the script otherwise you will get a `401 Unauthorized Access`.
40+
Download the repository. You can work inside it. Beware that the examples assume that they can access the file `./libtado/api.py`.
3241

33-
The latest `CLIENT_SECRET` can be found at [https://my.tado.com/webapp/env.js](https://my.tado.com/webapp/env.js). It will look something like this:
42+
Define a location and filename that will hold the credentials (refresh token) of your Tado login.
3443

35-
```js
36-
var TD = {
37-
config: {
38-
version: 'v588',
39-
oauth: {
40-
clientSecret: 'wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc'
41-
}
42-
}
43-
};
44-
```
44+
It is recommended to use a directory that only your application has access to, as the credentials file
45+
holds sensitive information!
4546

46-
An alternative way to get your `CLIENT_SECRET` is to enable the Developper Mode when logging in and catch the Headers. You will find the form data like this :
47+
Now you can call it in your Python script!
4748

48-
```yaml
49-
client_id: tado-web-app
50-
client_secret: fndskjnjzkefjNFRNkfKJRNFKRENkjnrek
51-
grant_type: password
52-
password: MyBeautifulPassword
53-
scope: home.user
54-
55-
```
49+
```python
50+
import libtado.api
51+
import webbrowser # only needed for direct web browser access
5652

57-
Then you just have to get the value in the attribute `client_secret`. You will need it to connect to your account through Tado APIs. The `client_secret` never dies so you can base your script on it.
53+
# TODO check
54+
t = api.Tado(token_file='/tmp/.libtado_refresh_token.json')
55+
# OR: t = api.Tado(saved_refresh_token='my_refresh_token')
5856

59-
## Usage
57+
status = t.get_device_activation_status()
6058

61-
Download the repository. You can work inside it. Beware that the examples assume that they can access the file `./libtado/api.py`.
59+
if status == "PENDING":
60+
url = t.get_device_verification_url()
6261

63-
Now you can call it in your Pyhton script!
62+
# to auto-open the browser (on a desktop device), un-comment the following line:
63+
# webbrowser.open_new_tab(url)
6464

65-
```python
66-
import libtado.api
65+
t.device_activation()
66+
67+
status = t.get_device_activation_status()
68+
69+
if status == "COMPLETED":
70+
print("Login successful")
71+
else:
72+
print(f"Login status is {status}")
6773

68-
t = api.Tado('[email protected]', 'myPassword', 'client_secret')
6974

7075
print(t.get_me())
7176
print(t.get_home())
7277
print(t.get_zones())
7378
print(t.get_state(1))
7479
```
7580

81+
The first time, the script will tell you to login to your Tado account.
82+
83+
It will show an output like:
84+
85+
```raw
86+
Please visit the following URL in your Web browser to log in to your Tado account: https://login.tado.com/oauth2/device?user_code=1234567
87+
Waiting for you to complete logging in. You have until yyyy-MM-dd hh:mm:ss
88+
.
89+
```
90+
91+
Complete your login before the time indicated.
92+
93+
Afterwards, the script should print the information from your Tado home.
94+
95+
If using the `token_file`, the next time you should not have to sign in.
96+
7697
## Examples
7798

7899
An example script is provided in the repository as `example.py`.

check.py

+15-4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,23 @@
44
from dotenv import load_dotenv
55
load_dotenv()
66

7+
TADO_REFRESH_TOKEN = os.environ["TADO_REFRESH_TOKEN"]
8+
TADO_CREDENTIALS_FILE = os.environ["TADO_CREDENTIALS_FILE"]
79

8-
TADO_USERNAME = os.environ["TADO_USERNAME"]
9-
TADO_PASSWORD = os.environ["TADO_PASSWORD"]
10-
TADO_CLIENT_SECRET = os.environ["TADO_CLIENT_SECRET"]
10+
tado = Tado(TADO_REFRESH_TOKEN, TADO_CREDENTIALS_FILE)
1111

12-
tado = Tado(TADO_USERNAME, TADO_PASSWORD, TADO_CLIENT_SECRET)
12+
status = tado.get_device_activation_status()
13+
if status == "PENDING":
14+
url = tado.get_device_verification_url()
15+
16+
tado.device_activation()
17+
18+
status = tado.get_device_activation_status()
19+
20+
if status == "COMPLETED":
21+
print("Login successful")
22+
else:
23+
print(f"Login status is {status}")
1324

1425
# print(tado.get_me())
1526
# print(tado.get_home())

docs/api/usage.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Usage:
55
``` { .python .select .copy }
66
import libtado.api
77
8-
api = tado.api('Username', 'Password')
8+
api = tado.api(token_file_path='/path/to/a/secure/folder/tado-credentials.json')
99
```
1010

1111
For API Reference see [API Reference](../api/reference.md)

docs/cli/configuration.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CLI Configuration
22

3+
!!! warning
4+
5+
The variables `TADO_USERNAME` and `TADO_PASSWORD` and `TADO_CLIENT_SECRET` are deprecated. Use `TADO_CREDENTIALS_FILE` or `TADO_REFRESH_TOKEN` instead.
6+
37
## Configuration File
48

59
There is no configuration file. No need.
@@ -8,9 +12,12 @@ There is no configuration file. No need.
812

913
The following environment variables are supported:
1014

11-
* `TADO_USERNAME` - Tado username
12-
* `TADO_PASSWORD` - Tado password
13-
* `TADO_CLIENT_SECRET` - Tado client secret
15+
* `TADO_CREDENTIALS_FILE` - Path to a file which holds your Tado credentials. Be careful: do not share this file with others.
16+
* `TADO_REFRESH_TOKEN` - Tado refresh token, from previous login. Valid for one-time use only.
17+
18+
!!! note
19+
20+
The variables `TADO_CREDENTIALS_FILE` and `TADO_REFRESH_TOKEN` are mutually exclusive. If both are set, an error will be raised.
1421

1522
Environment variables can be set up in multiples ways:
1623

docs/cli/usage.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@ Usage: tado [OPTIONS] COMMAND [ARGS]...
1313
1414
This script provides a command line client for the Tado API.
1515
16-
You can use the environment variables TADO_USERNAME and TADO_PASSWORD
17-
instead of the command line options.
16+
You can use the environment variables TADO_REFRESH_TOKEN and
17+
TADO_CREDENTIALS_FILE instead of the command line options.
18+
19+
The first time you will have to login using a browser. The command
20+
will show an URL to perform the login.
21+
22+
If using the credentials-file option or variable, the login will
23+
be stored so you don't have to do this next time.
1824
1925
Call 'tado COMMAND --help' to see available options for subcommands.
2026
2127
Options:
22-
-u, --username TEXT Tado username [required]
23-
-p, --password TEXT Tado password [required]
24-
-c, --client-secret TEXT Tado password [optional]
25-
-h, --help Show this message and exit.
28+
-f, --credentials-file TEXT Full path to a file in which the Tado credentials will be stored and read from [optional]
29+
-t, --refresh-token TEXT A Tado refresh token, retrieved from prior authentication with Tado [optional]
30+
-h, --help Show this message and exit.
2631
2732
Commands:
2833
capabilities Display the capabilities of a zone.

docs/faq.md

+9-19
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,18 @@
11
# FAQ
22

3-
## How to retrieve the client secret
3+
## Why variables `TADO_USERNAME` and `TADO_PASSWORD` are not working anymore?
44

5-
### Option #1: Do nothing
5+
Starting the 21st of March 2025, the Tado authentication workflow will definitely change to OAuth2 device code grant flow.
66

7-
The library wil automatically retrieve the client secret on following the steps at *Options #2*.
7+
!!! info
88

9-
### Option #2: From the application `env.js`
9+
Here is the link to the official announcement: [Tado Support Article - How do I authenticate to access the REST API?](https://support.tado.com/en/articles/8565472-how-do-i-authenticate-to-access-the-rest-api)
1010

11-
Retrieve the `CLIENT_SECRET` before running the script otherwise you will get a `401 Unauthorized Access`. The latest `CLIENT_SECRET` can be found at \[<https://my.tado.com/webapp/env.js>\](<https://my.tado.com/webapp/env.js>). It will look something like this:
11+
The direction that Tado is taking is to enforce security and privacy by using OAuth2. This is a good thing, as it will prevent the need to store your username and password in plain text in your environment variables.
12+
But the consequences of that change are that library handles differently the authentication process.
1213

13-
### Option #3: From the developer mode
14+
!!! warning
1415

15-
An alternative way to get your `CLIENT_SECRET` is to enable the Developper Mode when logging in and catch the Headers. You will find the form data like this:
16+
Now, you have to use the `TADO_CREDENTIALS_FILE` or `TADO_REFRESH` variables to authenticate.
1617

17-
```json
18-
{
19-
client_id: tado-web-app
20-
client_secret: fndskjnjzkefjNFRNkfKJRNFKRENkjnrek
21-
grant_type: password
22-
password: MyBeautifulPassword
23-
scope: home.user
24-
25-
}
26-
```
27-
28-
Then you just have to get the value in the attribute `client_secret`. You will need it to connect to your account through Tado APIs. The `client_secret` never dies so you can base your script on it.
18+
You can find more documentation on how to authenticate in the [**CLI Configuration**](./cli/configuration.md) section.

docs/getting-started/usage.md

+21-3
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,33 @@
33
After you installed libtado you can easily test it by using the included [`cli`](../cli/usage.md) like this:
44

55
``` { shell .copy }
6-
tado --username USERNAME --password PASSWORD whoami
6+
tado --credentials-file /path/to/a/secure/folder/tado-credentials.json whoami
77
```
88

99
To use the library in your own code you can start with this:
1010

1111
``` python
1212
import libtado.api
13-
t = tado.api('Username', 'Password')
14-
print(t.get_me())
13+
import webbrowser # only needed for direct web browser access
14+
15+
t = tado.api(token_file_path='/path/to/a/secure/folder/tado-credentials.json')
16+
17+
status = t.get_device_activation_status()
18+
19+
if status == "PENDING":
20+
url = t.get_device_verification_url()
21+
22+
# to auto-open the browser (on a desktop device), un-comment the following line:
23+
# webbrowser.open_new_tab(url)
24+
25+
t.device_activation()
26+
27+
status = t.get_device_activation_status()
28+
29+
if status == "COMPLETED":
30+
print("Login successful")
31+
else:
32+
print(f"Login status is {status}")
1533
```
1634

1735
Check out `all available API methods <api>` to learn what you can to with libtado.

docs/index.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ hide:
1212
}
1313
</style>
1414

15-
![Image title](./logo.png){: .center width=600 }
15+
![Libtado Logo](./logo.png){ .center width=400 style="display: block; margin-left: auto; margin-right: auto;" }
1616

1717
**libtado** is a simple Python library that provides methods to control the smart heating devices from the German company [tado GmbH](https://www.tado.com)[^1]. It uses the undocumented REST API of their website.
1818

@@ -22,7 +22,7 @@ The source code is hosted on [GitHub](https://github.com/germainlefebvre4/libtad
2222

2323
## License
2424

25-
> Copyright &copy; 2023 Germain Lefebvre, Max Rosin
25+
> Copyright &copy; 2025 Germain Lefebvre, Max Rosin
2626
>
2727
> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
2828
>

0 commit comments

Comments
 (0)