Skip to content

Commit

Permalink
readded password grant as an alternative
Browse files Browse the repository at this point in the history
  • Loading branch information
skrollme committed Dec 17, 2022
1 parent 8d592c5 commit 6630622
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 18 deletions.
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,18 @@ You can also configure this plugin via [ConfigUI-X's settings](https://github.co
"client_id": "XXXXX Create at https://dev.netatmo.com/",
"client_secret": "XXXXX Create at https://dev.netatmo.com/",
"refresh_token": "a valid refresh token for the given client_id",
"grant_type": "refresh_token"
... or if you use password-grant ...
"client_id": "XXXXX Create at https://dev.netatmo.com/",
"client_secret": "XXXXX Create at https://dev.netatmo.com/",
"username": "your netatmo account's mail-address",
"password": "your netatmo account's password",
"grant_type": "password"
}
}
],
```

- **weatherstation** Enables support for Netatmo's WeatherStation. Default value is *true*
Expand Down Expand Up @@ -80,15 +88,31 @@ If the whitelist contains at least one entry, all other ids will be excluded.

</pre>

### Retrieve _client_id_, _client_secret_ and _refresh_token_
## Netatmo API authentication
There are two methods to authenticate against the Netatmo API, but first 4 steps are always the same:

1. Register at http://dev.netatmo.com as a developer
2. After successful registration create your own app by using the menu entry "CREATE AN APP"
3. On the following page, enter a name for your app. Any name can be chosen. All other fields of the form (like _callback_url_, etc.) can be left blank.
4. After successfully submitting the form the overview page of your app should show _client_id_ and _client_secret_.

### "refresh_token" grant
This one is **recommended** by Netatmo because it is more secure since you do not have to store your username and password in homebridge's config file.
The downside is, that it is a little bit less stable, especially when homebridge is not running constantly.
This is because the plugin always gets a short-lived token to fetch data for some time. When the token expires, the plugin has to fetch a new one from the API.

5. Do an initial auth with the newly created app via the "Token generator" on your app's page https://dev.netatmo.com/apps/ to get a _refresh_token_
6. Add the _client_id_, the _client_secret_ and the _refresh_token_ to the config's _auth_-section
7. The plugin will use the _refresh_token_ from the config to retrieve and refresh _auth_tokens_. It will also store newly retrieved tokens in a file (_netatmo-token.js_) in your homebridge config directory. If you delete the _netatmo-token.js_ file, you may have to regenerate a new _refresh_token_ like in step 5) if your initial _refresh_token_ (from the _config.json_) already has expired

### "password" grant
This one is my preferred method, because in a single-user scenario and a most likely "at home and self-hosted"-setup it is totally fine for me. Netatmo deprecated this method but it is usable in cases where the user (here: homebridge) and the account (where the weatherstation is linked to) are the same.
Since this is the normal use-case for this homebridge-plugin I use this as long it is possible.

5. Add the _client_id_, the _client_secret_, the _username_ (your account email) and the _password_ (your account password) to the config's _auth_-section

### Retrieve _client_id_, _client_secret_ and _refresh_token_


## Siri Voice Commands

Expand Down
22 changes: 17 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,25 @@ class EveatmoPlatform {
this.log.warn('CAUTION! USING FAKE NETATMO API: ' + config.mockapi);
this.api = require("./lib/netatmo-api-mock")(config.mockapi);
} else {
if (config.auth.username || config.auth.password) {
throw new Error("username / password auth is not supported anymore! Please see the readme and use a 'refresh_token' instead.");
} else if (!config.auth.refresh_token) {
throw new Error("Authenticate 'refresh_token' not set.");
this.config.auth.grant_type = typeof config.auth.grant_type !== 'undefined' ? config.auth.grant_type : 'refresh_token';

if (this.config.auth.grant_type == 'refresh_token') {
if (config.auth.username || config.auth.password) {
throw new Error("'username' and 'password' are not used in grant_type 'refresh_token'");
} else if (!config.auth.refresh_token) {
throw new Error("'refresh_token' not set");
}
this.log.info("Authenticating using 'refresh_token' grant");
} else if (this.config.auth.grant_type == 'password') {
if (!config.auth.username || !config.auth.password) {
throw new Error("'username' and 'password' are mandatory when using grant_type 'password'");
}
this.log.info("Authenticating using 'password' grant");
} else {
throw new Error("Unsupported grant_type. Please use 'password' or 'refresh_token'");
}

this.api = new netatmo(config.auth, homebridge);
this.api = new netatmo(this.config.auth, homebridge);
}
this.api.on("error", function(error) {
this.log.error('ERROR - Netatmo: ' + error);
Expand Down
109 changes: 98 additions & 11 deletions lib/netatmo-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,24 @@ var filename;
var netatmo = function (args, homebridge) {
EventEmitter.call(this);

client_id = args.client_id;
client_secret = args.client_secret;
filename = homebridge.user.storagePath() + '/netatmo-token.json';
if (args.grant_type === 'refresh_token') {
client_id = args.client_id;
client_secret = args.client_secret;
filename = homebridge.user.storagePath() + '/netatmo-token.json';

if (fs.existsSync(filename)) {
let rawData = fs.readFileSync(filename);
let tokenData = JSON.parse(rawData);
access_token = tokenData.access_token;
refresh_token = tokenData.refresh_token;
} else {
refresh_token = args.refresh_token;
}

if (fs.existsSync(filename)) {
let rawData = fs.readFileSync(filename);
let tokenData = JSON.parse(rawData);
access_token = tokenData.access_token;
refresh_token = tokenData.refresh_token;
this.authenticate_refresh();
} else {
refresh_token = args.refresh_token;
this.authenticate(args, null);
}

this.authenticate_refresh();
};

util.inherits(netatmo, EventEmitter);
Expand Down Expand Up @@ -68,6 +72,89 @@ netatmo.prototype.handleRequestError = function (err, response, body, message, c
return error;
};

/**
* https://dev.netatmo.com/dev/resources/technical/guides/authentication
* @param args
* @param callback
* @returns {netatmo}
*/
netatmo.prototype.authenticate = function (args, callback) {
if (!args) {
this.emit("error", new Error("Authenticate 'args' not set."));
return this;
}

if (args.access_token) {
access_token = args.access_token;
return this;
}

if (!args.client_id) {
this.emit("error", new Error("Authenticate 'client_id' not set."));
return this;
}

if (!args.client_secret) {
this.emit("error", new Error("Authenticate 'client_secret' not set."));
return this;
}

if (!args.username) {
this.emit("error", new Error("Authenticate 'username' not set."));
return this;
}

if (!args.password) {
this.emit("error", new Error("Authenticate 'password' not set."));
return this;
}

username = args.username;
password = args.password;
client_id = args.client_id;
client_secret = args.client_secret;
scope = args.scope || 'read_station read_thermostat write_thermostat read_camera write_camera access_camera read_presence access_presence read_smokedetector read_homecoach';

var form = {
client_id: client_id,
client_secret: client_secret,
username: username,
password: password,
scope: scope,
grant_type: 'password',
};

var url = util.format('%s/oauth2/token', BASE_URL);

request({
url: url,
method: "POST",
form: form,
}, function (err, response, body) {
if (err || response.statusCode != 200) {
return this.handleRequestError(err, response, body, "Authenticate error", true);
}

body = JSON.parse(body);

access_token = body.access_token;

if (body.expires_in) {
setTimeout(this.authenticate_refresh.bind(this), body.expires_in * 1000, body.refresh_token);
}

this.emit('authenticated');

if (callback) {
return callback();
}

return this;
}.bind(this));

return this;
};

/**
* https://dev.netatmo.com/dev/resources/technical/guides/authentication/refreshingatoken
* @param refresh_token
Expand Down

0 comments on commit 6630622

Please sign in to comment.