Skip to content

Commit 80d886b

Browse files
authored
Merge pull request #261 from fronzbot/refactor-auth
Total refactoring of auth logic
2 parents 384c2f3 + 359d693 commit 80d886b

18 files changed

+858
-851
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
.tox/*
44
__pycache__/*
55
htmlcov/*
6+
.coverage
67
.coverage.*
78
coverage.xml
89
*.pyc

README.rst

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,59 +45,86 @@ This library was built with the intention of allowing easy communication with Bl
4545

4646
Quick Start
4747
=============
48-
The simplest way to use this package from a terminal is to call ``Blink.start()`` which will prompt for your Blink username and password and then log you in. Alternatively, you can instantiate the Blink class with a username and password, and call ``Blink.start()`` to login and setup without prompt, as shown below. In addition, http requests are throttled internally via use of the ``Blink.refresh_rate`` variable, which can be set at initialization and defaults to 30 seconds.
48+
The simplest way to use this package from a terminal is to call ``Blink.start()`` which will prompt for your Blink username and password and then log you in. In addition, http requests are throttled internally via use of the ``Blink.refresh_rate`` variable, which can be set at initialization and defaults to 30 seconds.
4949

5050
.. code:: python
5151
52-
from blinkpy import blinkpy
53-
blink = blinkpy.Blink(username='YOUR USER NAME', password='YOUR PASSWORD', refresh_rate=30)
52+
from blinkpy.blinkpy import Blink
53+
54+
blink = Blink()
5455
blink.start()
5556
56-
At startup, you may be prompted for a verification key. Just enter this in the command-line prompt. If you just receive a verification email asking to validate access for your device, enter nothing at this prompt. To avoid any command-line interaction, call the ``Blink`` class with the ``no_prompt=True`` flag. Instead, once you receive the verification email, call the following functions:
57+
58+
This flow will prompt you for your username and password. Once entered, if you likely will need to send a 2FA key to the blink servers (this pin is sent to your email address). When you receive this pin, enter at the prompt and the Blink library will proceed with setup.
59+
60+
Starting blink without a prompt
61+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
62+
In some cases, having an interactive command-line session is not desired. In this case, you will need to set the ``Blink.auth.no_prompt`` value to ``True``. In addition, since you will not be prompted with a username and password, you must supply the login data to the blink authentication handler. This is best done by instantiating your own auth handler with a dictionary containing at least your username and password.
5763

5864
.. code:: python
5965
60-
blink.login_handler.send_auth_key(blink, VERIFICATION_KEY)
66+
from blinkpy.blinkpy import Blink
67+
from blinkpy.auth import Auth
68+
69+
blink = Blink()
70+
# Can set no_prompt when initializing auth handler
71+
auth = Auth({"username": <your username>, "password": <your password>}, no_prompt=True)
72+
blink.auth = auth
73+
blink.start()
74+
75+
76+
Since you will not be prompted for any 2FA pin, you must call the ``blink.auth.send_auth_key`` function. There are two required parameters: the ``blink`` object as well as the ``key`` you received from Blink for 2FA:
77+
78+
.. code:: python
79+
80+
auth.send_auth_key(blink, <your key>)
6181
blink.setup_post_verify()
6282
63-
In addition, you can also save your credentials in a json file and initialize Blink with the credential file as follows:
83+
84+
Supplying credentials from file
85+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
86+
Other use cases may involved loading credentials from a file. This file must be ``json`` formatted and contain a minimum of ``username`` and ``password``. A built in function in the ``blinkpy.helpers.util`` module can aid in loading this file. Note, if ``no_prompt`` is desired, a similar flow can be followed as above.
6487

6588
.. code:: python
6689
67-
from blinkpy import blinkpy
68-
blink = blinkpy.Blink(cred_file="path/to/credentials.json")
90+
from blinkpy.blinkpy import Blink
91+
from blinkpy.auth import Auth
92+
from blinkpy.helpers.util import json_load
93+
94+
blink = Blink()
95+
auth = Auth(json_load("<File Location>"))
96+
blink.auth = auth
6997
blink.start()
7098
71-
The credential file must be json formatted with a ``username`` and ``password`` key like follows:
7299
73-
.. code:: json
100+
Saving credentials
101+
~~~~~~~~~~~~~~~~~~~
102+
This library also allows you to save your credentials to use in future sessions. Saved information includes authentication tokens as well as unique ids which should allow for a more streamlined experience and limits the frequency of login requests. This data can be saved as follows (it can then be loaded by following the instructions above for supplying credentials from a file):
103+
104+
.. code:: python
105+
106+
blink.save("<File location>")
74107
75-
{
76-
"username": "YOUR USER NAME",
77-
"password": "YOUR PASSWORD"
78-
}
79108
109+
Getting cameras
110+
~~~~~~~~~~~~~~~~
80111
Cameras are instantiated as individual ``BlinkCamera`` classes within a ``BlinkSyncModule`` instance. All of your sync modules are stored within the ``Blink.sync`` dictionary and can be accessed using the name of the sync module as the key (this is the name of your sync module in the Blink App).
81112

82113
The below code will display cameras and their available attributes:
83114

84115
.. code:: python
85116
86-
from blinkpy import blinkpy
87-
88-
blink = blinkpy.Blink(username='YOUR USER NAME', password='YOUR PASSWORD')
89-
blink.start()
90-
91117
for name, camera in blink.cameras.items():
92118
print(name) # Name of the camera
93119
print(camera.attributes) # Print available attributes of camera
94120
95-
The most recent images and videos can be accessed as a bytes-object via internal variables. These can be updated with calls to ``Blink.refresh()`` but will only make a request if motion has been detected or other changes have been found. This can be overridden with the ``force_cache`` flag, but this should be used for debugging only since it overrides the internal request throttling.
121+
122+
The most recent images and videos can be accessed as a bytes-object via internal variables. These can be updated with calls to ``Blink.refresh()`` but will only make a request if motion has been detected or other changes have been found. This can be overridden with the ``force`` flag, but this should be used for debugging only since it overrides the internal request throttling.
96123

97124
.. code:: python
98125
99126
camera = blink.cameras['SOME CAMERA NAME']
100-
blink.refresh(force_cache=True) # force a cache update USE WITH CAUTION
127+
blink.refresh(force=True) # force a cache update USE WITH CAUTION
101128
camera.image_from_cache.raw # bytes-like image object (jpg)
102129
camera.video_from_cache.raw # bytes-like video object (mp4)
103130
@@ -110,15 +137,15 @@ The ``blinkpy`` api also allows for saving images and videos to a file and snapp
110137
blink.refresh() # Get new information from server
111138
camera.image_to_file('/local/path/for/image.jpg')
112139
camera.video_to_file('/local/path/for/video.mp4')
113-
140+
141+
Download videos
142+
~~~~~~~~~~~~~~~~
114143
You can also use this library to download all videos from the server. In order to do this, you must specify a ``path``. You may also specifiy a how far back in time to go to retrieve videos via the ``since=`` variable (a simple string such as ``"2017/09/21"`` is sufficient), as well as how many pages to traverse via the ``page=`` variable. Note that by default, the library will search the first ten pages which is sufficient in most use cases. Additionally, you can specidy one or more cameras via the ``camera=`` property. This can be a single string indicating the name of the camera, or a list of camera names. By default, it is set to the string ``'all'`` to grab videos from all cameras.
115144

116145
Example usage, which downloads all videos recorded since July 4th, 2018 at 9:34am to the ``/home/blink`` directory:
117146

118147
.. code:: python
119148
120-
blink = blinkpy.Blink(username="YOUR USER NAME", password="YOUR PASSWORD")
121-
blink.start()
122149
blink.download_videos('/home/blink', since='2018/07/04 09:34')
123150
124151

blinkpy/api.py

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
import logging
44
from json import dumps
5-
import blinkpy.helpers.errors as ERROR
6-
from blinkpy.helpers.util import http_req, get_time, BlinkException, Throttle
5+
from blinkpy.helpers.util import get_time, Throttle
76
from blinkpy.helpers.constants import DEFAULT_URL
87

98
_LOGGER = logging.getLogger(__name__)
@@ -12,45 +11,31 @@
1211

1312

1413
def request_login(
15-
blink,
16-
url,
17-
username,
18-
password,
19-
notification_key,
20-
uid,
21-
is_retry=False,
22-
device_id="Blinkpy",
14+
auth, url, login_data, is_retry=False,
2315
):
2416
"""
2517
Login request.
2618
27-
:param blink: Blink instance.
19+
:param auth: Auth instance.
2820
:param url: Login url.
29-
:param username: Blink username.
30-
:param password: Blink password.
31-
:param notification_key: Randomly genereated key.
32-
:param uid: Randomly generated unique id key.
33-
:param is_retry: Is this part of a re-authorization attempt?
34-
:param device_id: Name of application to send at login.
21+
:login_data: Dictionary containing blink login data.
3522
"""
3623
headers = {"Host": DEFAULT_URL, "Content-Type": "application/json"}
3724
data = dumps(
3825
{
39-
"email": username,
40-
"password": password,
41-
"notification_key": notification_key,
42-
"unique_id": uid,
26+
"email": login_data["username"],
27+
"password": login_data["password"],
28+
"notification_key": login_data["notification_key"],
29+
"unique_id": login_data["uid"],
4330
"app_version": "6.0.7 (520300) #afb0be72a",
31+
"device_identifier": login_data["device_id"],
4432
"client_name": "Computer",
4533
"client_type": "android",
46-
"device_identifier": device_id,
47-
"device_name": "Blinkpy",
4834
"os_version": "5.1.1",
4935
"reauth": "true",
5036
}
5137
)
52-
return http_req(
53-
blink,
38+
return auth.query(
5439
url=url,
5540
headers=headers,
5641
data=data,
@@ -60,19 +45,14 @@ def request_login(
6045
)
6146

6247

63-
def request_verify(blink, verify_key):
48+
def request_verify(auth, blink, verify_key):
6449
"""Send verification key to blink servers."""
6550
url = "{}/api/v4/account/{}/client/{}/pin/verify".format(
6651
blink.urls.base_url, blink.account_id, blink.client_id
6752
)
6853
data = dumps({"pin": verify_key})
69-
return http_req(
70-
blink,
71-
url=url,
72-
headers=blink.auth_header,
73-
data=data,
74-
json_resp=False,
75-
reqtype="post",
54+
return auth.query(
55+
url=url, headers=auth.header, data=data, json_resp=False, reqtype="post",
7656
)
7757

7858

@@ -288,13 +268,10 @@ def http_get(blink, url, stream=False, json=True, is_retry=False):
288268
:param json: Return json response? TRUE/False
289269
:param is_retry: Is this part of a re-auth attempt?
290270
"""
291-
if blink.auth_header is None:
292-
raise BlinkException(ERROR.AUTH_TOKEN)
293271
_LOGGER.debug("Making GET request to %s", url)
294-
return http_req(
295-
blink,
272+
return blink.auth.query(
296273
url=url,
297-
headers=blink.auth_header,
274+
headers=blink.auth.header,
298275
reqtype="get",
299276
stream=stream,
300277
json_resp=json,
@@ -309,9 +286,7 @@ def http_post(blink, url, is_retry=False):
309286
:param url: URL to perfom post request.
310287
:param is_retry: Is this part of a re-auth attempt?
311288
"""
312-
if blink.auth_header is None:
313-
raise BlinkException(ERROR.AUTH_TOKEN)
314289
_LOGGER.debug("Making POST request to %s", url)
315-
return http_req(
316-
blink, url=url, headers=blink.auth_header, reqtype="post", is_retry=is_retry
290+
return blink.auth.query(
291+
url=url, headers=blink.auth.header, reqtype="post", is_retry=is_retry
317292
)

0 commit comments

Comments
 (0)