# HG changeset patch # User Peter Sanchez # Date 1599696780 25200 # Wed Sep 09 17:13:00 2020 -0700 # Node ID 203dac1b027b8ae3dc62a439d7f66e23ff701ee3 # Parent 9cd87dc6deb24139f463af91521b5ca9a0c621fa Updating README, adding license, adjusting setup, etc. diff --git a/BSD-LICENSE b/BSD-LICENSE new file mode 100644 --- /dev/null +++ b/BSD-LICENSE @@ -0,0 +1,32 @@ +Copyright (c) 2020, Netlandish Inc. +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. diff --git a/MANIFEST.in b/MANIFEST.in --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,4 @@ +include BSD-LICENSE +include README.rst include README.md recursive-include nlotp/templates * diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -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 @@ [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 diff --git a/nlotp/__init__.py b/nlotp/__init__.py --- a/nlotp/__init__.py +++ b/nlotp/__init__.py @@ -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) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 --- /dev/null +++ b/pyproject.toml @@ -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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Django>=3.0 +django-otp>=0.9.3 +qrcode>=6.1 diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -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 @@ "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, )