Skip to content

Commit 48e2e90

Browse files
authored
MFA using an Authenticator app (#292)
* MFA prototype * add mfa_providers abstract property * flesh out email provider a bit more * wip * flesh out email some more, and add authenticator * flesh out `AuthenticatorSeed` some more * add method for fetching pyotp * add `pyotp` to requirements * add proper auth methods for pyotp * start adding tests * bump minimum Piccolo version * test `create_new` method * add qrcode logic from @sinisaos PR * flesh out auth methods * lazy load `qrcode` * fleshing out logic some more in session login endpoint * change imports * make error messages consistent * add TODO * adding example app for testing * add `AuthenticatorProvider` to example app * added `get_registration_html` to provider * rename `seed_table` to `secret_table` * change params in `send_code` for authenticator * Create README.md * add `issuer_name` to `AuthenticatorProvider` * fix typo in docstring * add `get_registration_json` * add `get_registration_json` to base class * try embedding QR code image in HTML response * flesh out `MFARegisterEndpoint` endpoint * add todo about primary key * fix bugs in register endpoint * fix error - was returning html instead of json * show MFA code input on login page * Update README.md * Update tests.yaml * fix some linter errors * add `device_name` column to `AuthenticatorSecret` * make sure each provider has a custom token name * make each MFA Provider have a unique token name This means we can potentially put several MFA providers on the login page * If the user reused a code make sure auth fails (could be a replay attack) * add auth test for replay attacks * add `generate_recovery_code` * store recovery codes, and return recovery codes in endpoints (taken from @sinisaos example) * make sure recovery codes can be used to login * ignore mypy warnings for now * install pyotp in CI * endpoint test WIP * update `TestMFARegisterEndpoint` * remove todo * encrypt secret in db * use a proper template for MFA sign up * add links for where to download the authenticator app, and add JS for copying to clipboard * also test HTML register endpoint * add playwright tests * require a password to enable MFA * fix test * create separate template for cancelling MFA * initial docs * improve docs for AuthenticatorProvider params * add docs for tables * rename `mfa_register_endpoint` to `mfa_setup` So the name is more consistent with other endpoints in `piccolo_api` * improve docs, and rename from register to setup * remove debugging * improve template when MFA is disabled * add re-enabled link on disabled template * fix linter errors * use `self._auth_table` * render cancel template in GET endpoint if user is already enrolled * remove email for now * remove email from README * start moving encryption into its own file * update code to use encryption provider * improve the docstring for `mfa_setup` - mention rate limiting * improve docstrings * add params to docstrings * remove `device_name` - not currently used * change `revoke_all` to `revoke` The current design assumes a single device per user * add `XChaCha20Provider` * make sure pynacl is installed in tests * make sure pynacl is installed in tests (continued) * improve coverage * add `TestRevoke` * add a test to make sure auth works * remove unused import * add tests for recovery codes * remove breakpoint * fix bug with prefix * simplify encoding * changed login logic for multiple MFA providers * make `mfa_provider_name` param optional if there's only a single MFA provider * add `help_text` to `revoked_at` * add `valid_window` argument to `AuthenticatorProvider` * tell the user whether we sent them a code * increase coverage for `AuthenticatorSecret` * remove TODO in endpoint test * add tests for generating recovery codes * fix path to `AuthenticatorProvider` in docstring * add docs for encryption * remove imports * add docstring and type annotations to `get_b64encoded_qr_image`
1 parent cd4a8f4 commit 48e2e90

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2318
-9
lines changed

.github/workflows/tests.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ jobs:
2121
pip install -r requirements/requirements.txt
2222
pip install -r requirements/dev-requirements.txt
2323
pip install -r requirements/test-requirements.txt
24+
pip install -r requirements/extras/authenticator.txt
25+
pip install -r requirements/extras/pynacl.txt
26+
2427
- name: Lint
2528
run: ./scripts/lint.sh
2629

@@ -59,6 +62,8 @@ jobs:
5962
python -m pip install --upgrade pip
6063
pip install -r requirements/requirements.txt
6164
pip install -r requirements/test-requirements.txt
65+
pip install -r requirements/extras/authenticator.txt
66+
pip install -r requirements/extras/pynacl.txt
6267
- name: Test with pytest, Postgres
6368
run: ./scripts/test-postgres.sh
6469
env:
@@ -86,6 +91,8 @@ jobs:
8691
python -m pip install --upgrade pip
8792
pip install -r requirements/requirements.txt
8893
pip install -r requirements/test-requirements.txt
94+
pip install -r requirements/extras/authenticator.txt
95+
pip install -r requirements/extras/pynacl.txt
8996
- name: Test with pytest, SQLite
9097
run: ./scripts/test-sqlite.sh
9198
- name: Upload coverage

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ docs/source/_build/
1414
example_projects/token_auth/
1515
.env/
1616
.venv/
17+
18+
# Playwright
19+
videos/

docs/source/encryption/index.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Encryption
2+
==========
3+
4+
.. toctree::
5+
:maxdepth: 1
6+
7+
./introduction
8+
./providers
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Introduction
2+
============
3+
4+
Piccolo API provides some wrappers around popular encryption libraries.
5+
6+
These are current used by :ref:`Multifactor Authentication <MFA>`.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
Providers
2+
=========
3+
4+
.. currentmodule:: piccolo_api.encryption.providers
5+
6+
``EncryptionProvider``
7+
----------------------
8+
9+
.. autoclass:: EncryptionProvider
10+
11+
``FernetProvider``
12+
------------------
13+
14+
.. autoclass:: FernetProvider
15+
16+
``PlainTextProvider``
17+
---------------------
18+
19+
.. autoclass:: PlainTextProvider
20+
21+
``XChaCha20Provider``
22+
---------------------
23+
24+
.. autoclass:: XChaCha20Provider
25+
26+
-------------------------------------------------------------------------------
27+
28+
Dependencies
29+
------------
30+
31+
When first using some of the providers, you will be prompted to install the
32+
underlying encryption library.
33+
34+
For example, with ``XChaCha20Provider``, you need to install ``pynacl`` as
35+
follows:
36+
37+
.. code-block:: bash
38+
39+
pip install piccolo_api[pynacl]
40+
41+
-------------------------------------------------------------------------------
42+
43+
Example usage
44+
-------------
45+
46+
All of the providers work the same (except their parameters may be different).
47+
48+
Here's an example using ``XChaCha20Provider``:
49+
50+
.. code-block:: python
51+
52+
>>> from piccolo_api.encryption.providers import XChaCha20Provider
53+
54+
>>> encryption_key = XChaCha20Provider.get_new_key()
55+
>>> provider = XChaCha20Provider(encryption_key=encryption_key)
56+
57+
>>> encrypted = provider.encrypt("hello world")
58+
>>> print(provider.decrypt(encrypted))
59+
"hello world"
60+
61+
-------------------------------------------------------------------------------
62+
63+
Which provider to use?
64+
----------------------
65+
66+
``XChaCha20Provider`` is the most secure.
67+
68+
You may decide to use ``FernetProvider`` if you already have the Python
69+
``cryptography`` library as a dependency in your project.

docs/source/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ASGI app, covering authentication, security, and more.
2626

2727
./csp/index
2828
./csrf/index
29+
./encryption/index
2930
./rate_limiting/index
3031

3132
.. toctree::
@@ -35,6 +36,7 @@ ASGI app, covering authentication, security, and more.
3536
./which_authentication/index
3637
./jwt/index
3738
./session_auth/index
39+
./mfa/index
3840
./token_auth/index
3941
./register/index
4042
./change_password/index

docs/source/mfa/endpoints.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Endpoints
2+
=========
3+
4+
You must mount these ASGI endpoints in your app.
5+
6+
.. currentmodule:: piccolo_api.mfa.endpoints
7+
8+
``mfa_setup``
9+
-------------------------
10+
11+
.. autofunction:: mfa_setup
12+
13+
.. image:: images/mfa_register_endpoint.jpg
14+
15+
16+
``session_login``
17+
-----------------
18+
19+
Make sure you pass the ``mfa_providers`` argument to
20+
:func:`session_login <piccolo_api.session_auth.endpoints.session_login>`,
21+
so it knows to look for an MFA token.

docs/source/mfa/example.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Full Example
2+
============
3+
4+
Let's look at what an entire app looks like, which uses session auth, along
5+
with MFA (using the Authenticator provider).
6+
7+
-------------------------------------------------------------------------------
8+
9+
Starlette
10+
---------
11+
12+
.. include:: ../../../example_projects/mfa_demo/app.py
13+
:code: python
62.2 KB
Loading

docs/source/mfa/index.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.. _MFA:
2+
3+
Multi-Factor Authentication
4+
===========================
5+
6+
.. toctree::
7+
:maxdepth: 1
8+
9+
./introduction
10+
./endpoints
11+
./providers
12+
./tables
13+
./example

0 commit comments

Comments
 (0)