Updating README, adding license, adjusting setup, etc.
7 files changed, 172 insertions(+), 41 deletions(-)

A => BSD-LICENSE
M MANIFEST.in
M README.md
M nlotp/__init__.py
A => pyproject.toml
A => requirements.txt
M setup.py
A => BSD-LICENSE +32 -0
@@ 0,0 1,32 @@ 
+Copyright (c) 2020, Netlandish Inc. <https://www.netlandish.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or 
+without modification, are permitted provided that the 
+following conditions are met:
+
+ * Redistributions of source code must retain the above 
+   copyright notice, this list of conditions and the 
+   following disclaimer.
+
+ * Redistributions in binary form must reproduce the above 
+   copyright notice, this list of conditions and the following 
+   disclaimer in the documentation and/or other materials 
+   provided with the distribution.
+
+ * Neither the name of Netlandish Inc. nor the names of project
+   contributors may be used to endorse or promote products 
+   derived from this software without specific prior written 
+   permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

          
M MANIFEST.in +2 -0
@@ 1,2 1,4 @@ 
+include BSD-LICENSE
+include README.rst
 include README.md
 recursive-include nlotp/templates *

          
M README.md +65 -12
@@ 1,4 1,4 @@ 
-  nlotp [![nlshield](https://img.shields.io/badge/100%25-Netlandish-blue.svg?style=square-flat)](http://www.netlandish.com)
+django-nlotp [![nlshield](https://img.shields.io/badge/100%25-Netlandish-blue.svg?style=square-flat)](http://www.netlandish.com)
 ----
 
 Django application to quickly provide you with TOTP with recovery codes right

          
@@ 14,20 14,73 @@ out of the box.
 [issues]: https://todo.code.netlandish.com/~netlandish/django-nlotp
 [pinbox]: https://lists.code.netlandish.com/~netlandish/public-inbox
 
-# Netlandish One-Time Password (Two-Factor Authentication)
+## Netlandish One-Time Password (Two-Factor Authentication)
+
+The Django app `django-nlotp` (Netlandish OTP) that provides ready to use views
+to setup [TOTP][] support as well as recovery codes in your project. It's
+basically views and a middleware that operates with the `django-otp`
+([GitHub][django-otp]) app, which does the heavy lifting.
+
+We created this because we use `django-otp` in several products as well as
+client projects. After using it twice in a very similar fashion we knew we
+needed to write an app that we can plug in to virtually any product and
+immediately have access to [OTP][] support.
 
-Netlandish OTP is a customization of
-[`django-otp`](https://github.com/django-otp/django-otp) app, using TOTP and
-Static plugins.
+This app should give you everything you need to quickly implement OTP in your
+project. You can use settings for both `django-nlotp` and `django-otp` apps to
+customize as you wish. This app should give you coverage for the majority of
+use cases you have. If you want to get deeper into the weeds then we'd
+recommend rolling your own custom approach direct with `django-otp`.
+
+[otp]: http://en.wikipedia.org/wiki/One-time_password
+[totp]: https://tools.ietf.org/html/rfc6238
+[django-otp]: https://github.com/django-otp/django-otp
+
+## What is Two-Factor Verification?
+
+Two Factor Verification (2FA) makes your account more secure by requiring two
+things in order to log in: *something you know* and *something you own*.
+
+## How does it work?
+
+"Something you know" is your username and password, while "something you own"
+can be an application to generate a temporary code.
 
-The user verification process is done by first checking a TOTP device match, if
-not found look for user matching Static device (backup codes).
+Users who have chosen to set up 2FA will be asked to provide their second
+method of identity verification during the log in process.
+
+## How does 2FA with an authentication application (TOTP) work?
+
+Users can set up 2FA using any authentication application that supports
+the TOTP standard.
+
+TOTP authentication applications generate a regularly changing authentication
+code to use when logging into your account.
+
+Because TOTP is an open standard, there are many applications that are
+compatible with your AnyHow account. Popular applications include:
+
+- Google Authenticator for [Android][gadroid] or [iOS][gaios]
+- [Microsoft Authenticator][msauth]
+- [Authy][authy]
 
-To enable one-time password, a TOTP verification code needs to be provided, by
-scanning the QR code or entering a key on an application like Google
-Authenticator. Once enabled, backup codes (Static device) are generated and
-Django user account password will be required for two actions: generating new
-backup codes and disabling 2FA.
+There are many others as well.
+
+[gadroid]: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2
+[gaios]: https://itunes.apple.com/app/google-authenticator/id388497605
+[msauth]: https://www.microsoft.com/en-us/account/authenticator
+[authy]: https://authy.com/
+
+## User Verification
+
+By default in `django-nlotp` the user verification process (when the user is to
+enter their one time password) will check the password submitted in the
+following order:
+
+1. Check for a TOTP device match. In other words, check that the code matches
+   the configured user device.
+2. If it doesn't match, then it checks if the code is a user's backup code. If
+   so, it allows the user access and removes the provided code from future use.
 
 ## Installation
 

          
M nlotp/__init__.py +22 -1
@@ 1,3 1,24 @@ 
+# -*- coding: utf-8 -*-
 default_app_config = "nlotp.apps.NLOTPConfig"
+VERSION = (0, 1, 0, "final", 0)
+
+
+def get_version():
+    "Returns a PEP 386-compliant version number from VERSION."
+    assert len(VERSION) == 5
+    assert VERSION[3] in ("alpha", "beta", "rc", "final")
 
-__version__ = "0.1.0"
+    # Now build the two parts of the version number:
+    # main = X.Y[.Z]
+    # sub = .devN - for pre-alpha releases
+    #     | {a|b|c}N - for alpha, beta and rc releases
+
+    parts = 2 if VERSION[2] == 0 else 3
+    main = ".".join(str(x) for x in VERSION[:parts])
+
+    sub = ""
+    if VERSION[3] != "final":
+        mapping = {"alpha": "a", "beta": "b", "rc": "c"}
+        sub = mapping[VERSION[3]] + str(VERSION[4])
+
+    return str(main + sub)

          
A => pyproject.toml +29 -0
@@ 0,0 1,29 @@ 
+[tool.black]
+line-length = 79
+target-version = ['py37', 'py38']
+include = '\.pyi?$'
+exclude = '''
+
+(
+  /(
+      \.eggs         # exclude a few common directories in the
+    | \.git          # root of the project
+    | \.hg
+    | \.mypy_cache
+    | \.tox
+    | \.venv
+    | env
+    | _build
+    | buck-out
+    | build
+    | dist
+  )/
+  | foo.py           # also separately exclude a file named foo.py in
+                     # the root of the project
+)
+'''
+
+[tool.isort]
+line_length=79
+multi_line_output=5
+include_trailing_comma=true

          
A => requirements.txt +3 -0
@@ 0,0 1,3 @@ 
+Django>=3.0
+django-otp>=0.9.3
+qrcode>=6.1

          
M setup.py +19 -28
@@ 1,43 1,35 @@ 
 import os
 
-from setuptools import setup
+from setuptools import find_packages, setup
 
 project_name = "nlotp"
-long_description = open("README.md").read()
 
-# Idea from django-registration setup.py
-packages, data_files = [], []
-root_dir = os.path.dirname(__file__)
-if root_dir:
-    os.chdir(root_dir)
+if os.path.exists("README.rst"):
+    long_description = open("README.rst").read()
+else:
+    long_description = (
+        "See https://hg.code.netlandish.com/~netlandish/django-nlotp"
+    )
 
-for dirpath, dirnames, filenames in os.walk(project_name):
-    # Ignore dirnames that start with '.'
-    for i, dirname in enumerate(dirnames):
-        if dirname.startswith("."):
-            del dirnames[i]
-    if "__init__.py" in filenames:
-        pkg = dirpath.replace(os.path.sep, ".")
-        if os.path.altsep:
-            pkg = pkg.replace(os.path.altsep, ".")
-        packages.append(pkg)
-    elif filenames:
-        prefix = dirpath[(len(project_name) + 1):]
-        for f in filenames:
-            data_files.append(os.path.join(prefix, f))
 
+def parse_requirements(filename):
+    """ load requirements from a pip requirements file """
+    lineiter = (line.strip() for line in open(filename))
+    return [line for line in lineiter if line and not line.startswith("#")]
+
+
+reqs = parse_requirements("requirements.txt")
 
 setup(
     name=project_name,
-    version=__import__("nlotp").__version__,
+    version=__import__(project_name).get_version(),
     package_dir={project_name: project_name},
-    packages=packages,
-    package_data={project_name: data_files},
+    packages=find_packages(),
     description="Netlandish one-time passwords custom setup.",
     author="Netlandish Inc.",
-    author_email="gustavo@netlandish.com",
+    author_email="hello@netlandish.com",
     license="BSD License",
-    url="https://hg.code.netlandish.com/~netlandish/nlotp",
+    url="https://hg.code.netlandish.com/~netlandish/django-nlotp",
     long_description=long_description,
     platforms=["any"],
     classifiers=[

          
@@ 50,10 42,9 @@ setup(
         "Environment :: Web Environment",
         "Programming Language :: Python",
         "Programming Language :: Python :: 3 :: Only",
-        "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
     ],
-    install_requires=["Django>=3.0", "django-otp>=0.9.3", "qrcode>=6.1"],
+    install_requires=reqs,
 )