From 743ae59d545bdb80ce4536feda866facde0947d7 Mon Sep 17 00:00:00 2001
From: Jack Robison <jackrobison@lbry.io>
Date: Sat, 31 Mar 2018 18:42:57 -0400
Subject: [PATCH] fix lbrynet-cli when using authentication

-add explanation of daemon authentication to AuthJSONRPCServer docstring
-remove auth_required decorator, use auth for all api methods if use_authentication is true
-fix issues with the command line --http-auth flag to lbrynet-daemon and the use_http_auth setting in the config file
---
 CHANGELOG.md                    |   7 +++
 lbrynet/daemon/Daemon.py        |  28 ---------
 lbrynet/daemon/DaemonCLI.py     |  15 +++--
 lbrynet/daemon/DaemonControl.py |  24 +++----
 lbrynet/daemon/auth/auth.py     |   2 +-
 lbrynet/daemon/auth/client.py   | 107 ++++++++++++--------------------
 lbrynet/daemon/auth/server.py   | 104 ++++++++++++++-----------------
 lbrynet/daemon/auth/util.py     |  17 +++--
 8 files changed, 126 insertions(+), 178 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9605f478b..43701e481 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,9 @@ at anytime.
 ### Fixed
   * handling error from dht clients with old `ping` method
   * blobs not being re-announced if no peers successfully stored, now failed announcements are re-queued
+  * issue where an `AuthAPIClient` (used by `lbrynet-cli`) would fail to update its session secret and keep making new auth sessions, with every other request failing
+  * `use_auth_http` in a config file being overridden by the default command line argument to `lbrynet-daemon`, now the command line value will only override the config file value if it is provided
+  * `lbrynet-cli` not automatically switching to the authenticated client if the server is detected to be using authentication. This resulted in `lbrynet-cli` failing to run when `lbrynet-daemon` was run with the `--http-auth` flag
 
 ### Deprecated
   *
@@ -36,6 +39,8 @@ at anytime.
   * dht logging to be more verbose with errors and warnings
   * added `single_announce` and `last_announced_time` columns to the `blob` table in sqlite
   * pass the sd hash to reflector ClientFactory instead of looking it up
+  * if the `use_authentication` setting is configured, use authentication for all api methods instead of only those with the `auth_required` decorator
+  * regenerate api keys on startup if the using authentication
 
 ### Added
   * virtual kademlia network and mock udp transport for dht integration tests
@@ -45,6 +50,8 @@ at anytime.
 ### Removed
   * `announce_all` argument from `blob_announce`
   * old `blob_announce_all` command
+  * `AuthJSONRPCServer.auth_required` decorator
+  * unused `--wallet` argument to `lbrynet-daemon`, which used to be to support `PTCWallet`.
 
 
 ## [0.19.2] - 2018-03-28
diff --git a/lbrynet/daemon/Daemon.py b/lbrynet/daemon/Daemon.py
index 05d368b7d..44a87200c 100644
--- a/lbrynet/daemon/Daemon.py
+++ b/lbrynet/daemon/Daemon.py
@@ -1150,7 +1150,6 @@ class Daemon(AuthJSONRPCServer):
         """
         return self._render_response(conf.settings.get_adjustable_settings_dict())
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_settings_set(self, **kwargs):
         """
@@ -1495,7 +1494,6 @@ class Daemon(AuthJSONRPCServer):
         response = yield self._render_response(claim_results)
         defer.returnValue(response)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_resolve(self, force=False, uri=None, uris=[]):
         """
@@ -1586,7 +1584,6 @@ class Daemon(AuthJSONRPCServer):
         response = yield self._render_response(results)
         defer.returnValue(response)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_get(self, uri, file_name=None, timeout=None):
         """
@@ -1675,7 +1672,6 @@ class Daemon(AuthJSONRPCServer):
         response = yield self._render_response(result)
         defer.returnValue(response)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_file_set_status(self, status, **kwargs):
         """
@@ -1716,7 +1712,6 @@ class Daemon(AuthJSONRPCServer):
         response = yield self._render_response(msg)
         defer.returnValue(response)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_file_delete(self, delete_from_download_dir=False, delete_all=False, **kwargs):
         """
@@ -1797,7 +1792,6 @@ class Daemon(AuthJSONRPCServer):
         cost = yield self.get_est_cost(uri, size)
         defer.returnValue(cost)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_channel_new(self, channel_name, amount):
         """
@@ -1852,7 +1846,6 @@ class Daemon(AuthJSONRPCServer):
         response = yield self._render_response(result)
         defer.returnValue(response)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_channel_list(self):
         """
@@ -1874,7 +1867,6 @@ class Daemon(AuthJSONRPCServer):
         defer.returnValue(response)
 
     @AuthJSONRPCServer.deprecated("channel_list")
-    @AuthJSONRPCServer.auth_required
     def jsonrpc_channel_list_mine(self):
         """
         Get certificate claim infos for channels that can be published to (deprecated)
@@ -1891,7 +1883,6 @@ class Daemon(AuthJSONRPCServer):
 
         return self.jsonrpc_channel_list()
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_channel_export(self, claim_id):
         """
@@ -1910,7 +1901,6 @@ class Daemon(AuthJSONRPCServer):
         result = yield self.session.wallet.export_certificate_info(claim_id)
         defer.returnValue(result)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_channel_import(self, serialized_certificate_info):
         """
@@ -1929,7 +1919,6 @@ class Daemon(AuthJSONRPCServer):
         result = yield self.session.wallet.import_certificate_info(serialized_certificate_info)
         defer.returnValue(result)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_publish(self, name, bid, metadata=None, file_path=None, fee=None, title=None,
                         description=None, author=None, language=None, license=None,
@@ -2139,7 +2128,6 @@ class Daemon(AuthJSONRPCServer):
         response = yield self._render_response(result)
         defer.returnValue(response)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_claim_abandon(self, claim_id=None, txid=None, nout=None):
         """
@@ -2172,7 +2160,6 @@ class Daemon(AuthJSONRPCServer):
         self.analytics_manager.send_claim_action('abandon')
         defer.returnValue(result)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_claim_new_support(self, name, claim_id, amount):
         """
@@ -2200,7 +2187,6 @@ class Daemon(AuthJSONRPCServer):
         self.analytics_manager.send_claim_action('new_support')
         defer.returnValue(result)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_claim_renew(self, outpoint=None, height=None):
         """
@@ -2243,7 +2229,6 @@ class Daemon(AuthJSONRPCServer):
             result = yield self.session.wallet.claim_renew_all_before_expiration(height)
         defer.returnValue(result)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_claim_send_to_address(self, claim_id, address, amount=None):
         """
@@ -2276,7 +2261,6 @@ class Daemon(AuthJSONRPCServer):
         defer.returnValue(response)
 
     # TODO: claim_list_mine should be merged into claim_list, but idk how to authenticate it -Grin
-    @AuthJSONRPCServer.auth_required
     def jsonrpc_claim_list_mine(self):
         """
         List my name claims
@@ -2351,7 +2335,6 @@ class Daemon(AuthJSONRPCServer):
         claims = yield self.session.wallet.get_claims_for_name(name)
         defer.returnValue(claims)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_claim_list_by_channel(self, page=0, page_size=10, uri=None, uris=[]):
         """
@@ -2441,7 +2424,6 @@ class Daemon(AuthJSONRPCServer):
         response = yield self._render_response(results)
         defer.returnValue(response)
 
-    @AuthJSONRPCServer.auth_required
     def jsonrpc_transaction_list(self):
         """
         List transactions belonging to wallet
@@ -2521,7 +2503,6 @@ class Daemon(AuthJSONRPCServer):
         d.addCallback(lambda r: self._render_response(r))
         return d
 
-    @AuthJSONRPCServer.auth_required
     def jsonrpc_wallet_is_address_mine(self, address):
         """
         Checks if an address is associated with the current wallet.
@@ -2540,7 +2521,6 @@ class Daemon(AuthJSONRPCServer):
         d.addCallback(lambda is_mine: self._render_response(is_mine))
         return d
 
-    @AuthJSONRPCServer.auth_required
     def jsonrpc_wallet_public_key(self, address):
         """
         Get public key from wallet address
@@ -2560,7 +2540,6 @@ class Daemon(AuthJSONRPCServer):
         d.addCallback(lambda r: self._render_response(r))
         return d
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_wallet_list(self):
         """
@@ -2580,7 +2559,6 @@ class Daemon(AuthJSONRPCServer):
         response = yield self._render_response(addresses)
         defer.returnValue(response)
 
-    @AuthJSONRPCServer.auth_required
     def jsonrpc_wallet_new_address(self):
         """
         Generate a new wallet address
@@ -2604,7 +2582,6 @@ class Daemon(AuthJSONRPCServer):
         d.addCallback(lambda address: self._render_response(address))
         return d
 
-    @AuthJSONRPCServer.auth_required
     def jsonrpc_wallet_unused_address(self):
         """
         Return an address containing no balance, will create
@@ -2630,7 +2607,6 @@ class Daemon(AuthJSONRPCServer):
         return d
 
     @AuthJSONRPCServer.deprecated("wallet_send")
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_send_amount_to_address(self, amount, address):
         """
@@ -2659,7 +2635,6 @@ class Daemon(AuthJSONRPCServer):
         self.analytics_manager.send_credits_sent()
         defer.returnValue(True)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_wallet_send(self, amount, address=None, claim_id=None):
         """
@@ -2708,7 +2683,6 @@ class Daemon(AuthJSONRPCServer):
             self.analytics_manager.send_claim_action('new_support')
         defer.returnValue(result)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_wallet_prefill_addresses(self, num_addresses, amount, no_broadcast=False):
         """
@@ -2805,7 +2779,6 @@ class Daemon(AuthJSONRPCServer):
         d.addCallback(lambda r: self._render_response(r))
         return d
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_blob_get(self, blob_hash, timeout=None, encoding=None, payment_rate_manager=None):
         """
@@ -2849,7 +2822,6 @@ class Daemon(AuthJSONRPCServer):
         response = yield self._render_response(result)
         defer.returnValue(response)
 
-    @AuthJSONRPCServer.auth_required
     @defer.inlineCallbacks
     def jsonrpc_blob_delete(self, blob_hash):
         """
diff --git a/lbrynet/daemon/DaemonCLI.py b/lbrynet/daemon/DaemonCLI.py
index d8dd5404e..594a2f215 100644
--- a/lbrynet/daemon/DaemonCLI.py
+++ b/lbrynet/daemon/DaemonCLI.py
@@ -6,7 +6,7 @@ from docopt import docopt
 from collections import OrderedDict
 from lbrynet import conf
 from lbrynet.core import utils
-from lbrynet.daemon.auth.client import JSONRPCException, LBRYAPIClient
+from lbrynet.daemon.auth.client import JSONRPCException, LBRYAPIClient, AuthAPIClient
 from lbrynet.daemon.Daemon import LOADING_WALLET_CODE, Daemon
 from lbrynet.core.system_info import get_platform
 from jsonrpc.common import RPCError
@@ -93,12 +93,19 @@ def main():
         status = api.status()
     except URLError as err:
         if isinstance(err, HTTPError) and err.code == UNAUTHORIZED:
-            print_error("Daemon requires authentication, but none was provided.",
-                        suggest_help=False)
+            api = AuthAPIClient.config()
+            # this can happen if the daemon is using auth with the --http-auth flag
+            # when the config setting is to not use it
+            try:
+                status = api.status()
+            except:
+                print_error("Daemon requires authentication, but none was provided.",
+                            suggest_help=False)
+                return 1
         else:
             print_error("Could not connect to daemon. Are you sure it's running?",
                         suggest_help=False)
-        return 1
+            return 1
 
     status_code = status['startup_status']['code']
 
diff --git a/lbrynet/daemon/DaemonControl.py b/lbrynet/daemon/DaemonControl.py
index 2638bca4b..23cd04450 100644
--- a/lbrynet/daemon/DaemonControl.py
+++ b/lbrynet/daemon/DaemonControl.py
@@ -32,12 +32,6 @@ def start():
         type=str,
         default=None
     )
-    parser.add_argument(
-        "--wallet",
-        help="lbryum or ptc for testing, default lbryum",
-        type=str,
-        default=conf.settings['wallet']
-    )
     parser.add_argument(
         "--http-auth", dest="useauth", action="store_true", default=conf.settings['use_auth_http']
     )
@@ -82,23 +76,25 @@ def start():
 
     if test_internet_connection():
         analytics_manager = analytics.Manager.new_instance()
-        start_server_and_listen(args.useauth, analytics_manager)
+        start_server_and_listen(analytics_manager)
         reactor.run()
     else:
         log.info("Not connected to internet, unable to start")
 
 
 def update_settings_from_args(args):
-    conf.settings.update({
-        'use_auth_http': args.useauth,
-        'wallet': args.wallet,
-    }, data_types=(conf.TYPE_CLI,))
+    if args.conf:
+        conf.conf_file = args.conf
+
+    if args.useauth:
+        conf.settings.update({
+            'use_auth_http': args.useauth,
+        }, data_types=(conf.TYPE_CLI,))
 
-    conf.conf_file = args.conf
 
 
 @defer.inlineCallbacks
-def start_server_and_listen(use_auth, analytics_manager):
+def start_server_and_listen(analytics_manager):
     """
     Args:
         use_auth: set to true to enable http authentication
@@ -107,7 +103,7 @@ def start_server_and_listen(use_auth, analytics_manager):
     analytics_manager.send_server_startup()
     daemon_server = DaemonServer(analytics_manager)
     try:
-        yield daemon_server.start(use_auth)
+        yield daemon_server.start(conf.settings['use_auth_http'])
         analytics_manager.send_server_startup_success()
     except Exception as e:
         log.exception('Failed to start lbrynet-daemon')
diff --git a/lbrynet/daemon/auth/auth.py b/lbrynet/daemon/auth/auth.py
index b1a8ad702..368a4ccde 100644
--- a/lbrynet/daemon/auth/auth.py
+++ b/lbrynet/daemon/auth/auth.py
@@ -35,6 +35,7 @@ class PasswordChecker(object):
     @classmethod
     def load(cls, password_dict):
         passwords = {key: password_dict[key].secret for key in password_dict}
+        log.info("Loaded %i api key(s)", len(passwords))
         return cls(passwords)
 
     def requestAvatarId(self, creds):
@@ -45,4 +46,3 @@ class PasswordChecker(object):
                 return defer.succeed(creds.username)
         log.warning('Incorrect username or password')
         return defer.fail(cred_error.UnauthorizedLogin('Incorrect username or password'))
-
diff --git a/lbrynet/daemon/auth/client.py b/lbrynet/daemon/auth/client.py
index 3008bec8d..2e18ac748 100644
--- a/lbrynet/daemon/auth/client.py
+++ b/lbrynet/daemon/auth/client.py
@@ -1,13 +1,12 @@
-import urlparse
-import logging
-import requests
 import os
-import base64
 import json
-
-from lbrynet.daemon.auth.util import load_api_keys, APIKey, API_KEY_NAME, get_auth_message
-from lbrynet import conf
+import urlparse
+import requests
+from requests.cookies import RequestsCookieJar
+import logging
 from jsonrpc.proxy import JSONRPCProxy
+from lbrynet import conf
+from lbrynet.daemon.auth.util import load_api_keys, APIKey, API_KEY_NAME, get_auth_message
 
 log = logging.getLogger(__name__)
 USER_AGENT = "AuthServiceProxy/0.1"
@@ -16,6 +15,12 @@ LBRY_SECRET = "LBRY_SECRET"
 HTTP_TIMEOUT = 30
 
 
+def copy_cookies(cookies):
+    result = RequestsCookieJar()
+    result.update(cookies)
+    return result
+
+
 class JSONRPCException(Exception):
     def __init__(self, rpc_error):
         Exception.__init__(self)
@@ -23,25 +28,25 @@ class JSONRPCException(Exception):
 
 
 class AuthAPIClient(object):
-    def __init__(self, key, timeout, connection, count, cookies, auth, url, login_url):
+    def __init__(self, key, timeout, connection, count, cookies, url, login_url):
         self.__api_key = key
         self.__service_url = login_url
         self.__id_count = count
         self.__url = url
-        self.__auth_header = auth
         self.__conn = connection
-        self.__cookies = cookies
+        self.__cookies = copy_cookies(cookies)
 
     def __getattr__(self, name):
         if name.startswith('__') and name.endswith('__'):
-            raise AttributeError  # Python internal stuff
+            raise AttributeError(name)
 
         def f(*args):
             return self.call(name, args[0] if args else {})
 
         return f
 
-    def call(self, method, params={}):
+    def call(self, method, params=None):
+        params = params or {}
         self.__id_count += 1
         pre_auth_post_data = {
             'version': '2',
@@ -50,41 +55,27 @@ class AuthAPIClient(object):
             'id': self.__id_count
         }
         to_auth = get_auth_message(pre_auth_post_data)
-        token = self.__api_key.get_hmac(to_auth)
-        pre_auth_post_data.update({'hmac': token})
+        pre_auth_post_data.update({'hmac': self.__api_key.get_hmac(to_auth)})
         post_data = json.dumps(pre_auth_post_data)
-        service_url = self.__service_url
-        auth_header = self.__auth_header
-        cookies = self.__cookies
-        host = self.__url.hostname
-
-        req = requests.Request(method='POST',
-                               url=service_url,
-                               data=post_data,
-                               headers={
-                                   'Host': host,
-                                   'User-Agent': USER_AGENT,
-                                   'Authorization': auth_header,
-                                   'Content-type': 'application/json'
-                               },
-                               cookies=cookies)
-        r = req.prepare()
-        http_response = self.__conn.send(r)
-        cookies = http_response.cookies
-        headers = http_response.headers
-        next_secret = headers.get(LBRY_SECRET, False)
-        if next_secret:
-            self.__api_key.secret = next_secret
-            self.__cookies = cookies
-
+        cookies = copy_cookies(self.__cookies)
+        req = requests.Request(
+            method='POST', url=self.__service_url, data=post_data, cookies=cookies,
+            headers={
+                        'Host': self.__url.hostname,
+                        'User-Agent': USER_AGENT,
+                        'Content-type': 'application/json'
+            }
+        )
+        http_response = self.__conn.send(req.prepare())
         if http_response is None:
             raise JSONRPCException({
                 'code': -342, 'message': 'missing HTTP response from server'})
-
         http_response.raise_for_status()
-
+        next_secret = http_response.headers.get(LBRY_SECRET, False)
+        if next_secret:
+            self.__api_key.secret = next_secret
+            self.__cookies = copy_cookies(http_response.cookies)
         response = http_response.json()
-
         if response.get('error') is not None:
             raise JSONRPCException(response['error'])
         elif 'result' not in response:
@@ -94,13 +85,10 @@ class AuthAPIClient(object):
             return response['result']
 
     @classmethod
-    def config(cls, key_name=None, key=None, pw_path=None,
-               timeout=HTTP_TIMEOUT,
-               connection=None, count=0,
-               cookies=None, auth=None,
-               url=None, login_url=None):
+    def config(cls, key_name=None, key=None, pw_path=None, timeout=HTTP_TIMEOUT, connection=None, count=0,
+               cookies=None, auth=None, url=None, login_url=None):
 
-        api_key_name = API_KEY_NAME if not key_name else key_name
+        api_key_name = key_name or API_KEY_NAME
         pw_path = os.path.join(conf.settings['data_dir'], ".api_keys") if not pw_path else pw_path
         if not key:
             keys = load_api_keys(pw_path)
@@ -118,41 +106,28 @@ class AuthAPIClient(object):
         id_count = count
 
         if auth is None and connection is None and cookies is None and url is None:
-            # This is a new client instance, initialize the auth header and start a session
+            # This is a new client instance, start an authenticated session
             url = urlparse.urlparse(service_url)
-            (user, passwd) = (url.username, url.password)
-            try:
-                user = user.encode('utf8')
-            except AttributeError:
-                pass
-            try:
-                passwd = passwd.encode('utf8')
-            except AttributeError:
-                pass
-            authpair = user + b':' + passwd
-            auth_header = b'Basic ' + base64.b64encode(authpair)
             conn = requests.Session()
-            conn.auth = (user, passwd)
             req = requests.Request(method='POST',
                                    url=service_url,
-                                   auth=conn.auth,
                                    headers={'Host': url.hostname,
                                             'User-Agent': USER_AGENT,
-                                            'Authorization': auth_header,
                                             'Content-type': 'application/json'},)
             r = req.prepare()
             http_response = conn.send(r)
-            cookies = http_response.cookies
+            cookies = RequestsCookieJar()
+            cookies.update(http_response.cookies)
             uid = cookies.get(TWISTED_SESSION)
             api_key = APIKey.new(seed=uid)
         else:
             # This is a client that already has a session, use it
-            auth_header = auth
             conn = connection
-            assert cookies.get(LBRY_SECRET, False), "Missing cookie"
+            if not cookies.get(LBRY_SECRET):
+                raise Exception("Missing cookie")
             secret = cookies.get(LBRY_SECRET)
             api_key = APIKey(secret, api_key_name)
-        return cls(api_key, timeout, conn, id_count, cookies, auth_header, url, service_url)
+        return cls(api_key, timeout, conn, id_count, cookies, url, service_url)
 
 
 class LBRYAPIClient(object):
diff --git a/lbrynet/daemon/auth/server.py b/lbrynet/daemon/auth/server.py
index af08f8c46..3b3581099 100644
--- a/lbrynet/daemon/auth/server.py
+++ b/lbrynet/daemon/auth/server.py
@@ -13,7 +13,7 @@ from txjsonrpc import jsonrpclib
 from traceback import format_exc
 
 from lbrynet import conf
-from lbrynet.core.Error import InvalidAuthenticationToken
+from lbrynet.core.Error import InvalidAuthenticationToken, InvalidHeaderError
 from lbrynet.core import utils
 from lbrynet.daemon.auth.util import APIKey, get_auth_message
 from lbrynet.daemon.auth.client import LBRY_SECRET
@@ -119,15 +119,12 @@ class JSONRPCServerType(type):
         klass = type.__new__(mcs, name, bases, newattrs)
         klass.callable_methods = {}
         klass.deprecated_methods = {}
-        klass.authorized_functions = []
 
         for methodname in dir(klass):
             if methodname.startswith("jsonrpc_"):
                 method = getattr(klass, methodname)
                 if not hasattr(method, '_deprecated'):
                     klass.callable_methods.update({methodname.split("jsonrpc_")[1]: method})
-                    if hasattr(method, '_auth_required'):
-                        klass.authorized_functions.append(methodname.split("jsonrpc_")[1])
                 else:
                     klass.deprecated_methods.update({methodname.split("jsonrpc_")[1]: method})
         return klass
@@ -136,11 +133,6 @@ class JSONRPCServerType(type):
 class AuthorizedBase(object):
     __metaclass__ = JSONRPCServerType
 
-    @staticmethod
-    def auth_required(f):
-        f._auth_required = True
-        return f
-
     @staticmethod
     def deprecated(new_command=None):
         def _deprecated_wrapper(f):
@@ -151,26 +143,28 @@ class AuthorizedBase(object):
 
 
 class AuthJSONRPCServer(AuthorizedBase):
-    """Authorized JSONRPC server used as the base class for the LBRY API
+    """
+    Authorized JSONRPC server used as the base class for the LBRY API
 
     API methods are named with a leading "jsonrpc_"
 
-    Decorators:
-
-        @AuthJSONRPCServer.auth_required: this requires that the client
-            include a valid hmac authentication token in their request
-
     Attributes:
-        allowed_during_startup (list): list of api methods that are
-            callable before the server has finished startup
+        allowed_during_startup (list): list of api methods that are callable before the server has finished startup
+        sessions (dict): (dict): {<session id>: <lbrynet.daemon.auth.util.APIKey>}
+        callable_methods (dict): {<api method name>: <api method>}
 
-        sessions (dict): dictionary of active session_id:
-            lbrynet.lbrynet_daemon.auth.util.APIKey values
+    Authentication:
+        If use_authentication is true, basic HTTP and HMAC authentication will be used for all requests and the
+        service url will require a username and password.
 
-        authorized_functions (list): list of api methods that require authentication
-
-        callable_methods (dict): dictionary of api_callable_name: method values
+        To start an authenticated session a client sends an HTTP POST to <user>:<password>@<api host>:<api port>.
+        If accepted, the server replies with a TWISTED_SESSION cookie containing a session id and the message "OK".
+        The client initializes their shared secret for hmac to be the b64 encoded sha256 of their session id.
 
+        To send an authenticated request a client sends an HTTP POST to the auth api url with the TWISTED_SESSION
+        cookie and includes a hmac token in the message using the previously set shared secret. If the token is valid
+        the server will randomize the shared secret and return the new value under the LBRY_SECRET header, which the
+        client uses to generate the token for their next request.
     """
     implements(resource.IResource)
 
@@ -178,9 +172,7 @@ class AuthJSONRPCServer(AuthorizedBase):
     allowed_during_startup = []
 
     def __init__(self, use_authentication=None):
-        self._use_authentication = (
-            use_authentication if use_authentication is not None else conf.settings['use_auth_http']
-        )
+        self._use_authentication = use_authentication or conf.settings['use_auth_http']
         self.announced_startup = False
         self.sessions = {}
 
@@ -239,7 +231,9 @@ class AuthJSONRPCServer(AuthorizedBase):
 
     def _render(self, request):
         time_in = utils.now()
-        # assert self._check_headers(request), InvalidHeaderError
+        if not self._check_headers(request):
+            self._render_error(Failure(InvalidHeaderError()), request, None)
+            return server.NOT_DONE_YET
         session = request.getSession()
         session_id = session.uid
         finished_deferred = request.notifyFinish()
@@ -270,36 +264,36 @@ class AuthJSONRPCServer(AuthorizedBase):
             self._render_error(JSONRPCError(None, JSONRPCError.CODE_PARSE_ERROR), request, None)
             return server.NOT_DONE_YET
 
-        id_ = None
+        request_id = None
         try:
             function_name = parsed.get('method')
             args = parsed.get('params', {})
-            id_ = parsed.get('id', None)
+            request_id = parsed.get('id', None)
             token = parsed.pop('hmac', None)
         except AttributeError as err:
             log.warning(err)
             self._render_error(
-                JSONRPCError(None, code=JSONRPCError.CODE_INVALID_REQUEST), request, id_
+                JSONRPCError(None, code=JSONRPCError.CODE_INVALID_REQUEST), request, request_id
             )
             return server.NOT_DONE_YET
 
         reply_with_next_secret = False
         if self._use_authentication:
-            if function_name in self.authorized_functions:
-                try:
-                    self._verify_token(session_id, parsed, token)
-                except InvalidAuthenticationToken as err:
-                    log.warning("API validation failed")
-                    self._render_error(
-                        JSONRPCError.create_from_exception(
-                            err.message, code=JSONRPCError.CODE_AUTHENTICATION_ERROR,
-                            traceback=format_exc()
-                        ),
-                        request, id_
-                    )
-                    return server.NOT_DONE_YET
-                self._update_session_secret(session_id)
-                reply_with_next_secret = True
+            try:
+                self._verify_token(session_id, parsed, token)
+            except InvalidAuthenticationToken as err:
+                log.warning("API validation failed")
+                self._render_error(
+                    JSONRPCError.create_from_exception(
+                        err, code=JSONRPCError.CODE_AUTHENTICATION_ERROR,
+                        traceback=format_exc()
+                    ),
+                    request, request_id
+                )
+                return server.NOT_DONE_YET
+            request.addCookie("TWISTED_SESSION", session_id)
+            self._update_session_secret(session_id)
+            reply_with_next_secret = True
 
         try:
             fn = self._get_jsonrpc_method(function_name)
@@ -307,7 +301,7 @@ class AuthJSONRPCServer(AuthorizedBase):
             log.warning('Failed to get function %s: %s', function_name, err)
             self._render_error(
                 JSONRPCError(None, JSONRPCError.CODE_METHOD_NOT_FOUND),
-                request, id_
+                request, request_id
             )
             return server.NOT_DONE_YET
         except NotAllowedDuringStartupError:
@@ -315,7 +309,7 @@ class AuthJSONRPCServer(AuthorizedBase):
             self._render_error(
                 JSONRPCError("This method is unavailable until the daemon is fully started",
                              code=JSONRPCError.CODE_INVALID_REQUEST),
-                request, id_
+                request, request_id
             )
             return server.NOT_DONE_YET
 
@@ -341,7 +335,7 @@ class AuthJSONRPCServer(AuthorizedBase):
             log.warning(params_error_message)
             self._render_error(
                 JSONRPCError(params_error_message, code=JSONRPCError.CODE_INVALID_PARAMS),
-                request, id_
+                request, request_id
             )
             return server.NOT_DONE_YET
 
@@ -353,12 +347,9 @@ class AuthJSONRPCServer(AuthorizedBase):
         # request.finish() from being called on a closed request.
         finished_deferred.addErrback(self._handle_dropped_request, d, function_name)
 
-        d.addCallback(self._callback_render, request, id_, reply_with_next_secret)
-        # TODO: don't trap RuntimeError, which is presently caught to
-        # handle deferredLists that won't peacefully cancel, namely
-        # get_lbry_files
-        d.addErrback(trap, ConnectionDone, ConnectionLost, defer.CancelledError, RuntimeError)
-        d.addErrback(self._render_error, request, id_)
+        d.addCallback(self._callback_render, request, request_id, reply_with_next_secret)
+        d.addErrback(trap, ConnectionDone, ConnectionLost, defer.CancelledError)
+        d.addErrback(self._render_error, request, request_id)
         d.addBoth(lambda _: log.debug("%s took %f",
                                       function_name,
                                       (utils.now() - time_in).total_seconds()))
@@ -371,7 +362,7 @@ class AuthJSONRPCServer(AuthorizedBase):
         @param session_id:
         @return: secret
         """
-        log.info("Register api session")
+        log.info("Started new api session")
         token = APIKey.new(seed=session_id)
         self.sessions.update({session_id: token})
 
@@ -382,7 +373,8 @@ class AuthJSONRPCServer(AuthorizedBase):
     def _check_headers(self, request):
         return (
             self._check_header_source(request, 'Origin') and
-            self._check_header_source(request, 'Referer'))
+            self._check_header_source(request, 'Referer')
+        )
 
     def _check_header_source(self, request, header):
         """Check if the source of the request is allowed based on the header value."""
@@ -461,7 +453,7 @@ class AuthJSONRPCServer(AuthorizedBase):
         return None, None
 
     def _initialize_session(self, session_id):
-        if not self.sessions.get(session_id, False):
+        if not self.sessions.get(session_id):
             self._register_user_session(session_id)
             return True
         return False
diff --git a/lbrynet/daemon/auth/util.py b/lbrynet/daemon/auth/util.py
index eaef0896b..7db751248 100644
--- a/lbrynet/daemon/auth/util.py
+++ b/lbrynet/daemon/auth/util.py
@@ -46,12 +46,13 @@ class APIKey(object):
     def compare_hmac(self, message, token):
         decoded_token = base58.b58decode(token)
         target = base58.b58decode(self.get_hmac(message))
+
         try:
-            assert len(decoded_token) == len(target), "Length mismatch"
-            r = hmac.compare_digest(decoded_token, target)
+            if len(decoded_token) != len(target):
+                return False
+            return hmac.compare_digest(decoded_token, target)
         except:
             return False
-        return r
 
 
 def load_api_keys(path):
@@ -67,7 +68,6 @@ def load_api_keys(path):
         secret = key['secret']
         expiration = key['expiration']
         keys_for_return.update({key_name: APIKey(secret, key_name, expiration)})
-
     return keys_for_return
 
 
@@ -81,11 +81,10 @@ def save_api_keys(keys, path):
 
 
 def initialize_api_key_file(key_path):
-    if not os.path.isfile(key_path):
-        keys = {}
-        new_api_key = APIKey.new(name=API_KEY_NAME)
-        keys.update({new_api_key.name: new_api_key})
-        save_api_keys(keys, key_path)
+    keys = {}
+    new_api_key = APIKey.new(name=API_KEY_NAME)
+    keys.update({new_api_key.name: new_api_key})
+    save_api_keys(keys, key_path)
 
 
 def get_auth_message(message_dict):