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

Thoughts about an "accounts registration" plugin #1973

Closed
magopian opened this issue Jan 14, 2019 · 16 comments
Closed

Thoughts about an "accounts registration" plugin #1973

magopian opened this issue Jan 14, 2019 · 16 comments

Comments

@magopian
Copy link
Contributor

magopian commented Jan 14, 2019

Following some discussions, here's my rough thoughts on creating an "accounts registration" plugin. The use case is having a flow for users to
1/ register an account
2/ receive a "registration code" via email
2/ validate their email using this "registration code"
3/ reset their password if needed

Register an account

1/ anonymous POST on /accountsmgmt/register/<email>
2/ this creates a record {"id": email, "password": hash, "registration-code": uuid}
3/ an email is sent with a link to /accountsmgmt/register/validate/<registration-code>
4/ GETing /accountsmgmt/register/validate/<registration-code> returns the ID which is the username (the email)
5/ POSTing to /accountsmgmt/register/validate/<registration-code> creates a kinto account with the same id and password, and updates the current "register" record to mark it as used (or deletes it)

Resetting a password

1/ anonymous POST on acountsmgmt/resetpassword/<email>
2/ this creates a record {"id": email, "reset-code": uuid}
3/ an email is sent with a link to /accountsmgmt/resetpassword/reset/<reset-code>
4/ GETing /accountsmgmt/resetpassword/<reset-code> returns the ID which is the username (the email)
5/ POSTing the new password to /accountsmgmt/resetpassword/reset/<reset-code> updates the kinto account with the same ID, and updates the current "register" record to mark it as used (or deletes it)

Using the email (username) as the ID as the advantage of not having several registration or password reset codes laying around for the same user. The drawback is that we're not benefiting from the "kinto resource" management as we're GET/POST-ing on endpoints that are the registration or password reset codes, and not the IDs.

Not sure if that makes sense? Do you have any feedback, ideas or tips?

@magopian magopian changed the title Thoughts about a "accounts registration" plugin Thoughts about an "accounts registration" plugin Jan 14, 2019
@Natim
Copy link
Member

Natim commented Jan 15, 2019

A couple of notes

  • The namespace for the account plugin is accounts, why do you want to create a new namespace? Can't we use /accounts/<email>/reset-password?

  • I like the idea of using the email as a username because it ease the sharing capabilities of Kinto. We can share with account:[email protected] and all it takes for someone to open it would be to use the POST /accounts/<email> endpoint.

Alternative proposal

Registration

Rather than creating new resources to manage the registration, I would be ok to actually add a activated flag to an account.

{"id": "[email protected]", "password": "bcrypt_key", "activated": false}

We can have a settings to activate email validation, that will enforce email as usernames.

When we do a POST on /accounts/<email> it would create the following object:

{"id": "[email protected]", "password": "bcrypt_key", "activated": false}

With the bcrypt_key being the generated password for activation.

To activate an account we would use:

http POST /accounts/<email> password="newpassword" --auth "<email>:activation_key"

It is the current procedure to change a password, that would activate the account by authenticating with the generated link and providing a new password.

When an account is deactivated, the only endpoint allowed with the activation_key would be the change password endpoint.

Reset password

For the reset password phase, we could use the /accounts/<email>/reset-password endpoint. It might be good to actually use another object for this case since we don't want to erase the user previous password. We can use the cache to store this key with the niceties to automatically get rid of it after a TTL period.

We could still use: http POST /accounts/<email> password="newpassword" --auth "<email>:reset_key" to apply the change.

This would means that for this given endpoint we would accept the reset key from the cache as an alternative password to allow a user to change their password.

@leplatrem
Copy link
Contributor

We can have a settings to activate email validation, that will enforce email as usernames.

I like the idea of extending the accounts plugin with email/registration features.

It would require pyramid_mailer (or Kinto emailer?) to be installed though.

The field "activated": false could work, but we need it to be read-only when modified via the HTTP API

I think that using the cache backend for activation/reset tokens would work. If users take too much time to activate the account, and they must revisit the activation/reset endpoint..

@magopian
Copy link
Contributor Author

{"id": "[email protected]", "password": "bcrypt_key", "activated": false}

With the bcrypt_key being the generated password for activation.

To activate an account we would use:

http POST /accounts/ password="newpassword" --auth ":activation_key"

I was wondering about this: it means there's no way to ask for a username AND password on account creation, which is a somewhat widespread use case.
Would it make sense to instead have the activation key stored in a activation-key field, which, if present, means the account hasn't been activated yet?
This way you'd create the account as usual, and the activation_key field would be automatically generated and added to the record. POSTing the activation key to /accounts/<email>/validate would then remove the activation-key field from the user record, activating it.

A remaining question is: how do we deal with the email? If we want a simple link, then we need to validate the account using a simple GET (which means not providing a new password, which means using the alternate method using a validate endpoint). I believe this wouldn't be a good practice (applying some data change on a GET request).

We could also imagine having an optional activation-form-url field in the user record. If this field is provided when creating the user, GETing the /accounts/<email>/validate/<activation key>/ endpoint (with an anonymous user) would redirect to the activation-form-url/<activation key> url, where a custom form could be displayed (allowing for @Natim method using the "change password" procedure).

@Natim
Copy link
Member

Natim commented Jan 15, 2019

it means there's no way to ask for a username AND password on account creation, which is a somewhat widespread use case.

Fair point.

If we want a simple link, then we need to validate the account using a simple GET (which means not providing a new password, which means using the alternate method using a validate endpoint)

This is actually a feature, I don't think one should link to the validation link on the kinto service directly, because you want to present some kind of UI to the user.

Instead the GET link should link to the APP that will make the POST to the validation endpoint. My understanding is that we might want to ask for the password at this stage as well, so that one could use the same UI for changing the password, resetting the password and setting the first password.

@magopian
Copy link
Contributor Author

magopian commented Jan 15, 2019

How about that: when the "account validation" option is enabled, it requires an extra activation-form-url field to be added to the user.
When the user is created, the activation key is appended to this field, and a mail is sent with the content of this field.

To activate the user, this activation key needs to be POSTed to the /accounts/<email>/validate endpoint, which will remove the activation-form-url field from the user record.

Eg:
1/ echo '{"data": {"password": "azerty123", "activation-form-url": "https://example.com/validate/"}}' | http PUT http://localhost:8888/v1/accounts/[email protected]
2/ user record is created (user is not active):

{"data": {
  "password": "azerty123",
  "activation-form-url": "https://example.com/validate/123456789abc"
}}

3/ an email is sent with the link https://example.com/validate/123456789abc, following it leads to a custom form that will POST the activation key to kinto
4/ http POST http://localhost:8888/v1/accounts/[email protected]/validate/ activation_key="123456789abc"
5/ user record is modified (user is active):

{"data": {
  "password": "azerty123"
}}

How does that sound? It does feel a bit brittle and bespoke, not sure that API makes sense...

Other paths to explore:

  • having the activation form url set in the settings (but this means there's only one of those URLs per kinto instance, which might be a limitation to some use cases)
  • go back to @Natim's proposition: use the "change password" procedure if we believe it's ok to not create a user with a password (and ask the user for the password later on, on the activation form). In that case, the "temporary/activation" password is appended to the activation form url link sent by mail.

@magopian
Copy link
Contributor Author

I just realized: @Natim we can't use the password as the activation key, as it's stored hashed in the record, not in plain text.

@Natim
Copy link
Member

Natim commented Jan 17, 2019

I don't see why it is a problem, because you can hash the one provided to validate it as we currently do with the password.

@magopian
Copy link
Contributor Author

How do you pass the activation key (the password) to the user (to the custom validation form)?

@Natim
Copy link
Member

Natim commented Jan 17, 2019

You generate a key, you hash it you store it hashed in the database, you return it, the user uses it, you hash it then you validate it with the stored hashed one.

@magopian
Copy link
Contributor Author

and if the user loses the mail or whatever, there's no way to recreate the link? What happens then, the username is "lost" to everybody?

@Natim
Copy link
Member

Natim commented Jan 17, 2019

If the email is lost you can recreate an account and get an new activation key. If you used the activation key you can use the reset password mechanism to change it again.

If you loose the username, well it depends, what that means but I guess you can still create a new account and ask an admin to give you privileges.

@magopian
Copy link
Contributor Author

Implementation question: is there a way to specify a different resource schema given the settings?
I'd like a different resource schema for the Account class if the 'account activation' option is enabled in the settings, is that possible?

@leplatrem
Copy link
Contributor

I'd like a different resource schema for the Account class if the 'account activation' option is enabled in the settings, is that possible?

Yes there must be way with colander defer, or pyramid views scanning.. I wouldn't bother with that for now ;)

@magopian
Copy link
Contributor Author

magopian commented Jan 17, 2019

I've started a WIP in https://github.com/Kinto/kinto/compare/master...magopian:1973-account-validation?expand=1

It would require pyramid_mailer (or Kinto emailer?) to be installed though.

@leplatrem I believe kinto emailer wouldn't work here out of the box because it's missing:
1/ a way to send to the user id (the list of recipients can only be an email address or a group URI, it's not accepting a placeholder like {id}
2/ a way to use random fields as placeholders in the template (in our case the activation-form-url and activation-key fields)

So either I can update the kinto-emailer plugin to add those modifications, or use the pyramid_mailer.

Thoughts?

@Natim
Copy link
Member

Natim commented Jan 18, 2019

I think it is fine not relying on kinto-emailer for that especially because kinto-emailer is a kinto plugin with a focus on notifications regarding resource updates.

+1 to use pyramid_mailer directly.

@leplatrem
Copy link
Contributor

+1 to use pyramid_mailer directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants