Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add account validation capability to the accounts plugin #1982

Merged
merged 36 commits into from Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7f4c17f
Require a 'validation-form-url' and set a 'validation-key' fields if …
magopian Jan 15, 2019
ccfda31
Add a 'validate' endpoint to the accounts
magopian Jan 17, 2019
3418be2
Add 'account-validation' to the capabilities
magopian Jan 17, 2019
4535965
Add some tests
magopian Jan 17, 2019
332503f
Require user id to be an email when enabling account validation
magopian Jan 18, 2019
03cdb03
Send a validation email on account creation
magopian Jan 18, 2019
23303d7
@almet review: use a 'validated' field on the user record
magopian Jan 21, 2019
f156626
@natim review: store the activation-key in the cache
magopian Jan 21, 2019
6699098
Add the new settings to the kinto.tpl file
magopian Jan 22, 2019
4d02593
Document the account validation option, fill in the changelog
magopian Jan 22, 2019
966f9c1
Don't require an activation-form-url, render using the optional email…
magopian Jan 24, 2019
f4c408c
Send a confirmation email when the account has been activated
magopian Jan 25, 2019
5f53edb
Send a mail with a temporary reset password when POSTing to the reset…
magopian Jan 25, 2019
f58684c
Can use a cached reset-password to change a user's password
magopian Jan 25, 2019
c97ea40
Add documentation for pyramid_mailer configuration
magopian Jan 28, 2019
c874997
Rebase on master following 13.0.0 release
magopian Jan 28, 2019
0833c37
Fix code coverage
magopian Jan 28, 2019
1b175bd
review: validation_enabled as a sub-capability
magopian Feb 7, 2019
c0b3d36
review: conditional loading of the validate and reset-password views
magopian Feb 7, 2019
7fd8b52
Update with master
magopian Feb 7, 2019
19d030a
Move most of the documentation from the API (accounts.rst) to the set…
magopian Feb 8, 2019
947d78d
Some more documentation modifications
magopian Feb 8, 2019
9f16d46
Simplify the template ini file
magopian Feb 8, 2019
d27de8c
re-add the setup.py which was removed by mistake
magopian Feb 8, 2019
bc0af23
Remove unused import
magopian Feb 8, 2019
cb8b480
Some more code reorganization following review
magopian Feb 8, 2019
ad96885
Restrict the reset password to changing an account's password
magopian Feb 11, 2019
1940f8b
Move the code to send activation email to a listener
magopian Feb 11, 2019
caec88d
Move the code to send activation confirmation email to a listener
magopian Feb 11, 2019
80a7cae
Move all the mail sending related code to a separate module
magopian Feb 11, 2019
fd4fff3
@natim review
magopian Feb 12, 2019
17911d1
Allow the '+' character in the account IDs
magopian Feb 12, 2019
768acb7
Investigate py36-raw error.
Feb 12, 2019
49c9e43
Remove a repetition in the docs
magopian Feb 21, 2019
9309fb5
Use a patch when changing the password or the user is 'reset'
magopian Feb 21, 2019
bfde10d
Some more reviews from @leplatrem
magopian Mar 19, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -18,3 +18,4 @@ kinto/plugins/admin/node_modules
kinto/plugins/admin/coverage
kinto/plugins/admin/npm-debug.log
.pytest_cache/
mail/
16 changes: 9 additions & 7 deletions CHANGELOG.rst
Expand Up @@ -20,6 +20,15 @@ This document describes changes between each past release.

- Fixed spelling and Filtering docs

**New features**

- Expose the user_profile in the user field of the hello page. (#1989)
Natim marked this conversation as resolved.
Show resolved Hide resolved
- Add an "account validation" option to the accounts plugin. (#1973)
- Add a ``validate`` endpoint at ``/accounts/{user id}/validate/{validation
key}`` which can be used to validate an account when the :ref:`account
validation <accounts-validate>` option is enabled on the accounts plugin.

API is now at version **1.22**. See `API changelog`_.

13.0.1 (2019-01-29)
-------------------
Expand All @@ -28,7 +37,6 @@ This document describes changes between each past release.

- Loosen up the Content-Security policies in the Kinto Admin plugin to prevent Webpack inline script to be rejected (fixes #2000)


13.0.0 (2019-01-25)
-------------------

Expand All @@ -45,11 +53,6 @@ This document describes changes between each past release.

- Upgrade kinto-admin to v1.23.0

**New features**

- Expose the user_profile in the user field of the hello page. (#1989)


12.0.2 (2019-01-25)
-------------------

Expand All @@ -66,7 +69,6 @@ This document describes changes between each past release.
- Fix bumping of tombstones timestamps when deleting objects in PostgreSQL storage backend (fixes #1981)
- Fix ETag header in responses of DELETE on plural endpoints (ref #1981)


12.0.0 (2019-01-10)
-------------------

Expand Down
116 changes: 114 additions & 2 deletions docs/api/1.x/accounts.rst
Expand Up @@ -147,14 +147,14 @@ Alternatively, accounts can be created using POST. Supply the user id and passw

.. sourcecode:: bash

$ echo '{"data": {"id": "bob", password": "azerty123"}}' | http POST http://localhost:8888/v1/accounts --verbose
$ echo '{"data": {"id": "bob", "password": "azerty123"}}' | http POST http://localhost:8888/v1/accounts --verbose

.. note::

Depending on the :ref:`configuration <settings-accounts>`, you may not be allowed to create accounts.


.. _accounts-udpate:
.. _accounts-update:

Change password
===============
Expand Down Expand Up @@ -294,3 +294,115 @@ Or delete some account:
::

$ http DELETE http://localhost:8888/v1/accounts/sam-body --auth admin:s3cr3t



.. _accounts-validate:

Validate accounts
=================

When the ``account_validation`` option is enabled in :ref:`the settings
<settings-account-validation>`, account IDs need to be valid email addresses:
they need to match the regexp in the ``account_validation.email_regexp``
setting. The default one is very generic, but you may restrict it to only allow
certain emails, for example only ones from a specific domain.

To make sure the ``account_validation`` is enabled, you can check if the
``validation_enabled`` flag is ``true`` in the ``"accounts"`` field on the
:ref:`root URL <api-utilities-hello>`.

.. http:post:: /accounts/(user_id)/validate/(activation_key)

:synopsis: Activates a newly created account with the ``account_validation`` option enabled.

**Anonymous**

**Example Request**

.. sourcecode:: bash

$ http POST http://localhost:8888/v1/accounts/bob@example.com/validate/2fe7a389-3556-4c8f-9513-c26bfc5f160b --verbose


.. sourcecode:: http

POST /v1/accounts/bob@example.com/validate/2fe7a389-3556-4c8f-9513-c26bfc5f160b HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 0
Host: localhost:8888
User-Agent: HTTPie/0.9.8

**Example Response**

.. sourcecode:: http

HTTP/1.1 200 OK
Access-Control-Expose-Headers: Content-Length, Retry-After, Backoff, Alert
Content-Length: 195
Content-Type: application/json
Date: Mon, 21 Jan 2019 13:41:17 GMT
Server: waitress
X-Content-Type-Options: nosniff

{
"id": "bob@example.com",
"last_modified": 1548077982793,
"password": "$2b$12$zlTlYet5v.v57ak2gEYyoeqKSGzLvwXF/.v3DGpT/q69LecHv68gm",
"validated": true
}

.. _accounts-reset-password:

Resetting a forgotten password
==============================

If the ``account_validation`` option in :ref:`the settings
<settings-account-validation>` has been enabled, a temporary reset password may
be requested through the endpoint available at `/accounts/(user
id)/reset-password`.
magopian marked this conversation as resolved.
Show resolved Hide resolved

.. http:post:: /accounts/(user_id)/reset-password

:synopsis: Require a temporary reset password for an account with the ``account_validation`` option enabled.
magopian marked this conversation as resolved.
Show resolved Hide resolved

**Anonymous**

**Example Request**

.. sourcecode:: bash

$ http POST http://localhost:8888/v1/accounts/bob@example.com/reset-password --verbose


.. sourcecode:: http

POST /v1/accounts/bob@example.com/reset-password HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 0
Host: localhost:8888
User-Agent: HTTPie/0.9.8


**Example Response**

.. sourcecode:: http

HTTP/1.1 200 OK
Access-Control-Expose-Headers: Backoff, Alert, Retry-After, Content-Length
Content-Length: 62
Content-Type: application/json
Date: Fri, 08 Feb 2019 14:04:15 GMT
Server: waitress
X-Content-Type-Options: nosniff

{
"message": "A temporary reset password has been sent by mail"
}

Using this temporary reset password, one can
:ref:`update the account <accounts-update>` providing the new password.
9 changes: 9 additions & 0 deletions docs/api/index.rst
Expand Up @@ -11,6 +11,15 @@ API
Changelog
---------

1.22 (unreleased)
'''''''''''''''''

- new `/accounts/{user id}/validate/{validation key}` endpoint when the
magopian marked this conversation as resolved.
Show resolved Hide resolved
`account validation` option is enabled for the accounts plugin. See
:ref:`account validation <accounts-validate>` (#1973)
- new `/accounts/{user id}/reset-password` endpoint to request a temporary
magopian marked this conversation as resolved.
Show resolved Hide resolved
reset password by email (#1973)

1.21 (2019-01-10)
'''''''''''''''''

Expand Down
166 changes: 166 additions & 0 deletions docs/configuration/settings.rst
Expand Up @@ -569,6 +569,172 @@ You can set ``account_create_principals`` if you want to limit account creation

See the :ref:`API docs <api-accounts>` to create accounts, change passwords etc.

.. _settings-account-validation:

**About account validation**

You can enable the :ref:`account validation <accounts-validate>` option, which
will require account IDs to be valid email addresses, to which a validation
email will be sent with an activation key.

.. code-block:: ini

kinto.account_validation = true
# Mail configuration:
# Set the sender for the validation email.
kinto.account_validation.email_sender = "admin@example.com"
magopian marked this conversation as resolved.
Show resolved Hide resolved

.. note::

Both the account validation and password reset need a properly configured
smtp server.
magopian marked this conversation as resolved.
Show resolved Hide resolved
To use a debug or testing mailer you may use the ``mail.mailer = debug`` or
``mail.mailer = testing`` settings. Refer to
`pyramid_mailer's configuration <https://docs.pylonsproject.org/projects/pyramid_mailer/en/latest/#configuration>`_.

You can restrict the email addresses allowed using the
``account_validation.email_regexp`` setting, and the delay for which the
activation key will be valid:

.. code-block:: ini

# Set the regular expression used to validate a proper email address.
kinto.account_validation.email_regexp = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you accept + character

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's an "additional restriction", it doesn't take over/replace what's declared in https://github.com/Kinto/kinto/blob/master/kinto/plugins/accounts/views.py#L33 (if that's the root cause of this issue, not sure yet, I'm investigating)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe shall we use the same regex for both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's the same use case: in one case (no account validation) you want to allow a few set of characters, in the other case you might want to restrict emails to a given sub-domain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed a fix

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's the same use case: in one case (no account validation) you want to allow a few set of characters, in the other case you might want to restrict emails to a given sub-domain.

Yeah makes sense 👍

# Set the "time to live" for the activation key stored in the cache. After that
# delay the account won't be activable anymore.
kinto.account_validation.validation_key_cache_ttl_seconds = 604800 # 7 days in seconds.

Once :ref:`created <accounts-create>`, the account will need to be activated
before being able to authenticate, using the ``validate`` endpoint and the
``activation-key`` sent by email.

.. sourcecode:: bash

$ echo '{"data": {"id": "bob@example.com", "password": "azerty123"}}' | http POST http://localhost:8888/v1/accounts --verbose

If the user was created, an email was sent to the user with the activation key,
which needs to be POSTed to the ``validate`` endpoint.

Example email:

::

Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
From: admin@example.com
Subject: activate your account
To: bob@example.com
Content-Disposition: inline

2fe7a389-3556-4c8f-9513-c26bfc5f160b

It is the responsability of the administrator to tell the mail recipient how to
validate the account by modifying the email body template in the settings.

This could be done by providing a link to a webapp that displays a form to the
user with a call to action to validate the user, which will POST the activation
key to the ``validate`` endpoint.

Or the email could explain how to copy the activation code and paste it in some
settings window.

The templates for the email subject and body can be customized:

.. code-block:: ini

kinto.account_validation.email_subject_template = "Account activation"
kinto.account_validation.email_body_template = "Hello {id},\n you can now activate your account using the following link:\n {form-url}{activation-key}"

... and they will be ``String.format``-ted with the content of the user, an
optional additional ``email-context`` provided alongside the user object, and
the ``activation-key``:

.. sourcecode:: bash

$ echo '{"data": {"id": "bob@example.com", "password": "azerty123", "email-context": {"name": "Bob Smith", "form-url": "https://example.com/validate/"}}}' | http POST http://localhost:8888/v1/accounts --verbose

magopian marked this conversation as resolved.
Show resolved Hide resolved
Whatever the means, a POST to the
``/accounts/(user_id)/validate/(activation_key)`` will validate and activate
the user.

Once the account is validated, another email will be sent for confirmation,
rendered using the same ``email-context``.

.. code-block:: ini

kinto.account_validation.email_confirmation_subject_template = "Account active"
kinto.account_validation.email_confirmation_body_template = "Your account {id} is now active"

**About password reset**

When the :ref:`account validation <accounts-validate>` option is enabled, an
additional endpoint is available at ``/accounts/(user id)/reset-password`` to
require a temporary reset password by email.

.. sourcecode:: bash

$ http POST http://localhost:8888/v1/accounts/bob@example.com/reset-password --verbose

Example email:

::

Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
From: admin@example.com
Subject: Reset password
To: mathieu@agopian.info
Content-Disposition: inline

b8ae48e6-709e-4f01-bfb9-bca9464cdcfc

The template used for the email subject and body can be customized using the
following settings:

.. code-block:: ini

kinto.account_validation.email_reset_password_subject_template = "Temporary reset password for {id}"
kinto.account_validation.email_reset_password_body_template = "Hello {id},\n you can use the following temporary reset password to change your password\n{reset-password}"

Those templates will be rendered using the user record fields, an optional
additional ``email-context`` provided alongside the user object, and the
``reset-password``.


It is the responsability of the administrator to tell the mail recipient how to
change their password using this temporary password.

This could be done by providing a link to a webapp that displays a form to the
user asking for the new password and a call to action, which will POST the
new password to the ``accounts/(user_id)`` endpoint.

The templates for the email subject and body can be customized:

.. code-block:: ini

kinto.account_validation.email_reset_password_subject_template = "Reset your password"
kinto.account_validation.email_reset_password_body_template = "Hello {id},\n you can set a new password for your account following this link:\n https://example.com/{reset-password}"

... and they will be ``String.format``-ted with the content of the user, an
optional additional ``email-context`` provided alongside the user object, and
the ``reset-password``:

.. sourcecode:: bash

$ echo '{"data": {"email-context": {"name": "Bob Smith"}}}' | http POST http://localhost:8888/v1/accounts/bob@example.com/reset-password --verbose

Using this temporary reset password, one can
:ref:`update the account <accounts-update>` providing the new password.

This temporary reset password will be valid for the amount of seconds set in
the settings:

.. code-block:: ini

# Set the "time to live" for the reset password stored in the cache.
kinto.account_validation.reset_password_cache_ttl_seconds = 604800 # 7 days in seconds.

.. _settings-openid:

Expand Down
2 changes: 1 addition & 1 deletion kinto/__init__.py
Expand Up @@ -14,7 +14,7 @@
__version__ = pkg_resources.get_distribution(__package__).version

# Implemented HTTP API Version
HTTP_API_VERSION = "1.21"
HTTP_API_VERSION = "1.22"

# Main kinto logger
logger = logging.getLogger(__name__)
Expand Down
12 changes: 12 additions & 0 deletions kinto/config/kinto.tpl
Expand Up @@ -99,6 +99,18 @@ kinto.account_create_principals = system.Everyone
kinto.account_write_principals = account:admin
# Allow administrators to create buckets
kinto.bucket_create_principals = account:admin
# Enable the "account_validation" option.
# kinto.account_validation = true
# Set the sender for the validation email.
# kinto.account_validation.email_sender = "admin@example.com"
# Set the regular expression used to validate a proper email address.
# kinto.account_validation.email_regexp = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"

# Mail configuration (needed for the account validation option), see https://docs.pylonsproject.org/projects/pyramid_mailer/en/latest/#configuration
# mail.host = localhost
# mail.port = 25
# mail.username = someusername
# mail.password = somepassword

# Notifications
# https://kinto.readthedocs.io/en/latest/configuration/settings.html#notifications
Expand Down