Skip to content

Commit

Permalink
Source credentials from module instance in config
Browse files Browse the repository at this point in the history
  • Loading branch information
m-roberts committed Jan 29, 2020
1 parent 1fefc55 commit fb8a382
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 263 deletions.
8 changes: 7 additions & 1 deletion MMM-Fitbit2.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ Module.register("MMM-Fitbit2",{

// Default module config.
defaults: {
credentials: {
client_id: "",
client_secret: ""
},
resources: [
"steps",
"caloriesOut",
Expand Down Expand Up @@ -81,6 +85,8 @@ Module.register("MMM-Fitbit2",{
get_data_payload = {}

config = {}
config.client_id = this.config.credentials.client_id
config.client_secret = this.config.credentials.client_secret
config.resources = this.config.resources
config.debug = this.config.debug

Expand All @@ -105,7 +111,7 @@ Module.register("MMM-Fitbit2",{
this.sendSocketNotification("GET DATA", get_data_payload);
},

// Checks whether the user wants to lookup a resourse type
// Checks whether the user wants to lookup a resource type
inResources: function(resource) {
return this.config.resources.indexOf(resource) > -1;
},
Expand Down
36 changes: 25 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,36 @@ cd ~/MagicMirror # or whatever your path to Magic Mirror is
cd modules
git clone https://github.com/m-roberts/MMM-Fitbit2
cd MMM-Fitbit2
./setup.sh
./init.sh # Install dependencies
# For each user
./auth.sh
```

### `setup.sh`
When you run `setup.sh`, you'll be asked to provide your personal `client_id` and `client_secret` information - this will then be stored in `python/credentials.ini` for future use.
### `auth.sh`
When you run `auth.sh`, you'll be asked to provide your personal `client_id` and `client_secret` information.

After this, a web browser will launch on your display (even if you ran `auth.sh` over SSH). You'll need to log in using your Fitbit username and password, if you are not logged in already. Make sure to allow access to all options and close the window when instructed.

After this, a web browser will launch on your display (even if you ran `setup.sh` over SSH). You'll need to log in using your Fitbit username and password, if you are not logged in already. Make sure to allow access to all options and close the window when instructed.
Once you have done this, you should have a file called `python/tokens-<client_id>.ini`. If you have this, you have finished authorisation!

Once you have done this, you should have a file called `python/tokens.ini`. If you have this, you have finished installation!
**Note: generating tokens for MMM-Fitbit2 will nullify all previously generated tokens associated with a given `client_id`. If you want to have a Fitbit account working with MMM-Fitbit2 on more than one device, you should copy your tokens file to the same file path, rather than generating new tokens.**

Configuration
---
After installing MMM-Fitbit2, you will need to include the module in your MagicMirror configuration.

Add the example config below to your config file in `~/MagicMirror/config/config.json` (or whatever your path to your Magic Mirror config is).
Add the example config below to your config file in `~/MagicMirror/config/config.json` (or whatever your path to your Magic Mirror config is), making sure to swap out `client_id` and `client_secret` with your own.

````javascript
{
module: "MMM-Fitbit2",
position: "top_center",
config: {
credentials: {
client_id: "<your client id>",
client_secret: "<your client secret>",
},
resources: [
"steps",
"caloriesOut",
Expand Down Expand Up @@ -113,16 +122,15 @@ This must not be done too often otherwise the rate limit will be exceeded, and F
Files
--
### .ini (generated)
* `credentials.ini` - OAuth 2.0 credentials. Used to generate access tokens.
* `tokens.ini` - Access token data generated by OAuth 2.0 credentials. Used to retrieve data from Fitbit API.

### Python (internal)
* `iniHandler.py` - Reads and writes `.ini` files
* `setupAccess.py` - Used by `setup.sh` to generate `tokens.ini` from `credentials.ini`.
* `getData.py` - Uses `python-fitbit` Python library to get Fitbit API data, using credentials from `tokens.ini`. Prints out in JSON to be parsed by `node_helper.js`.
* `token_handler.py` - Reads and writes token `.ini` files
* `setup_access.py` - Used by `auth.sh` to generate token `.ini` files
* `get_data.py` - Uses `python-fitbit` Python library to get Fitbit API data, using credentials from `tokens.ini`. Prints out in JSON to be parsed by `node_helper.js`.

### Javascript (used by MagicMirror)
* `node_helper.js` - Calls `getData.py`, passes it to `MMM-Fitbit2.js`.
* `node_helper.js` - Calls `get_data.py`, passes it to `MMM-Fitbit2.js`.
* `MMM-Fitbit2.js` - Receives data from `node_helper.js` and injects it into DOM

### CSS
Expand All @@ -131,3 +139,9 @@ Files
TODO
---
See [here](TODO.md).

<!-- Uninstalling/Revoking Access
---
This section is yet to be written...
Delete tokens file. Go to Fitbit dashboard, remove permissions to access data.
-->
8 changes: 3 additions & 5 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ TODO
==

### High priority
* Move from `credentials.ini` to sourcing from `config.js`
* Store tokens separately for each `client_id` (e.g. `tokens-13CD5J.ini`)
* Debugging: Add option for parameters instead of via stdin for manual execution in `getData.py`
* Debugging: Add option for parameters instead of via stdin for manual execution in `get_data.py`
* Improve look
* Fix spacing and text, etc.
* Remove "zzz" from sleep, keep spacing
Expand All @@ -18,7 +16,7 @@ TODO
### Medium priority
* Migrate to using Python 3
* Python 2 officially deprecated
* Fix iniHandler saving tokens
* Fix token_handler saving tokens
* Check that nothing breaks if data is missing
* e.g. no weight data for 30 days
* e.g. no heart data for the day
Expand Down Expand Up @@ -55,4 +53,4 @@ TODO

## Troubleshooting
### Invalid refresh token
If you are getting `oauthlib.oauth2.rfc6749.errors.InvalidGrantError: (invalid_grant)` when running `getData.py`, it likely means that your access token has expired and your refresh token is not the latest one associated with your account. Try running `setupAccess.py` again, and seeing if this helps.
If you are getting `oauthlib.oauth2.rfc6749.errors.InvalidGrantError: (invalid_grant)` when running `get_data.py`, it likely means that your access token has expired and your refresh token is not the latest one associated with your account. Try running `setup_access.py` again, and seeing if this helps.
15 changes: 15 additions & 0 deletions auth.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash -e

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

echo "AUTH.SH: OAuth 2.0 credentials: Fetch..."
echo "Enter 'OAuth 2.0 Client ID' (client_id):"
read client_id

echo "Enter 'Client Secret' (client_secret):"
read client_secret

echo "AUTH.SH: Generate access tokens: Opening web browser..."
cd "${DIR}/python"
DISPLAY=:0 python setup_access.py "${client_id}" "${client_secret}" 2> /dev/null
echo "AUTH.SH: Generate access tokens: Done!"
13 changes: 13 additions & 0 deletions init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash -e

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

cd "${DIR}"

echo "INIT.SH: Install dependencies: Node.js..."
npm install

echo "INIT.SH: Install dependencies: Python..."
pip install --quiet --user -r "${DIR}/requirements.txt"

echo "INIT.SH: Install dependencies: Done!"
4 changes: 2 additions & 2 deletions node_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = NodeHelper.create({

getData: function (config) {
const self = this;
const fileName = "getData.py";
const fileName = "get_data.py";

if (config.debug) {
console.log("MMM-Fitbit2: Data to receive: " + JSON.stringify(config));
Expand All @@ -33,7 +33,7 @@ module.exports = NodeHelper.create({
}
);

// Pass data to get from API to getData.py via stdin
// Pass data to get from API to get_data.py via stdin
fitbitPyShell.send(JSON.stringify(config))

// Return response from API
Expand Down
29 changes: 19 additions & 10 deletions python/getData.py → python/get_data.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python
import fitbit
import json
from iniHandler import set_debug_state, print_data, print_json, ReadCredentials, ReadTokens, WriteTokens
from token_handler import set_client_id, set_debug_state, print_data, print_json, read_tokens, write_tokens
from sys import stdin
import select

Expand Down Expand Up @@ -31,9 +31,13 @@ def handle_key_error(key_error, resource=None):

debug_mode = False

client_id = None
client_secret = None

resource_list = []

# Attempt to determine what data to get by
# reading an array passed in to stdin
resource_list = []
if select.select([stdin, ], [], [], 0.0)[0]:
try:
print_json("status", "Attempting to read data from stdin")
Expand All @@ -48,7 +52,11 @@ def handle_key_error(key_error, resource=None):

print_json("status", "Parsed stdin - extracting data")

debug_mode = config.get("debug", False)
debug_mode = config.get("debug", debug_mode)

client_id = config.get("client_id", client_id)
client_secret = config.get("client_secret", client_secret)

resource_list = config.get("resources", resource_list)
except SyntaxError as err:
warning_text = ("Debug mode and resource list"
Expand All @@ -59,17 +67,18 @@ def handle_key_error(key_error, resource=None):
print_json("status", "Nothing to read from stdin - using defaults")

set_debug_state(debug_mode)
print_json("status", "Debug mode", str(debug_mode))
print_json("status", "Debug Mode", str(debug_mode))

set_client_id(client_id)
print_json("status", "Client ID", str(client_id))

print_json("debug", "Client Secret", str(client_secret))

resource_list_str = ", ".join(resource_list) \
if len(resource_list) > 0 else "All"
print_json("status", "Resources to get", resource_list_str)

client_id, client_secret = ReadCredentials()
print_json("debug", "client_id", client_id)
print_json("debug", "client_secret", client_secret)

access_token, refresh_token, expires_at = ReadTokens()
access_token, refresh_token, expires_at = read_tokens()
print_json("debug", "access_token", access_token)
print_json("debug", "refresh_token", refresh_token)
print_json("debug", "expires_at", expires_at)
Expand All @@ -85,7 +94,7 @@ def WriteTokenWrapper(token):
acc_tok = token["access_token"]
ref_tok = token["refresh_token"]
expires_at = token["expires_at"]
WriteTokens(acc_tok, ref_tok, expires_at)
write_tokens(acc_tok, ref_tok, expires_at)

print_json("debug", "Creating authorised client")
authd_client = fitbit.Fitbit(client_id,
Expand Down
Loading

0 comments on commit fb8a382

Please sign in to comment.