feathers-plus / authentication-local-management

Adds sign up verification, forgotten password reset, and other capabilities to local feathers-authentication
MIT License
14 stars 2 forks source link

Discussing enhancements and changes for V3 rewrite #1

Open eddyystop opened 5 years ago

eddyystop commented 5 years ago

Transfer from https://github.com/feathers-plus/feathers-authentication-management/issues/114

eddyystop commented 5 years ago

Upgrade instructions will be added as needed to https://github.com/feathers-plus/authentication-local-management/blob/master/misc/upgrading.md

Breaking changes to feathers-authentication-management have been made.

eddyystop commented 5 years ago

@claustres @beeplin you may want to audit and comment on the ongoing changes mentioned in the above module.

The first change moves the hashing and comparing of tokens to outside the repo.

eddyystop commented 5 years ago

Move this here from feathers-authentication-management https://github.com/feathers-plus/feathers-authentication-management/issues/99

eddyystop commented 5 years ago

This is a summary of the changes. Please read the details at https://github.com/feathers-plus/authentication-local-management/blob/master/misc/upgrading.md

Previous status report. Converted feathers-authentication-management to async/await.

New status report. Outstanding issues in feathers-authentication-management have been addressed. Enhancements have been added.

Breaking changes

Enhancements

Documentation

Next steps:

eddyystop commented 5 years ago

I need eyes to consider these changes, which are further described in https://github.com/feathers-plus/authentication-local-management/blob/master/misc/upgrading.md

@claustres @beeplin ^^^

claustres commented 5 years ago

Customization capabilities now seem great (hashing, service calls, etc.).

Async/Await is really great as well.

I wonder what you mean by Hooks are now automatically configured on the a-l-m service. ?

Also you explain which operations are accessible by (un)authenticated users like resendVerifySignup, etc. Is this just an example or is it somewhat hard-coded ? Indeed I think others setup are possible.

Well done master !

eddyystop commented 5 years ago

The service hooks are configured by default as follows

const actionsNoAuth = [
  'resendVerifySignup', 'verifySignupLong', 'verifySignupShort',
  'sendResetPwd', 'resetPwdLong', 'resetPwdShort'
];

const defaultOptions = {
  authManagementHooks: { before: { create: authManagementHook } },
};

async function authManagementHook(context) {
  if (!context.data || !actionsNoAuth.includes(context.data.action)) {
    context = await authenticate('jwt')(context);
  }

  context.data.authUser = context.params.user;
  context.data.provider = context.params.provider;
  return context;
}

 app.service('authManagement').hooks(options.authManagementHooks);
eddyystop commented 5 years ago

This is a summary of the changes. Please read the details at https://github.com/feathers-plus/authentication-local-management/blob/master/misc/upgrading.md

claustres commented 5 years ago

If you can configure on a per operation basis which one requires auth or not by overriding the options then it is great.

eddyystop commented 5 years ago

options.authManagementHooks allows you to do that. Basically pass a modified version of the above hook.

NickBolles commented 5 years ago

I'm implementing f-a-m right now and found this. Is there any chance of publishing a beta npm package? It looks a lot easier to implement.

Another point I would suggest is creating Typescript Typings for the package. I'd be willing to chip in on them.

eddyystop commented 5 years ago

I won't be publishing a "beta" package on npm. You of course could do so for yourself.

f-a-m is stable, decently documented and has 2 sets of articles on Medium. You will finish your project using it.

I'm not sure a-l-m will be faster to implement considering the uncertainties involved. My guess is you'd spend fewer hours using f-a-m and later converting to a-l-m

Its a benefit for me if someone tests a-l-m, but it may impact the time to finish the project.

I'll take you up on embedding TS in the repo as you convert to a-l-m. It would have to be strict typings so its compatible with generated cli+ code.

eddyystop commented 5 years ago

New status report. Reference implementation with browser test bed completed.

Modifications made to previously documented changes

Breaking changes

Enhancements

Next steps:

Detailed docs

These are only a summary of the changes. You can read the details in the migration document https://github.com/feathers-plus/authentication-local-management/blob/master/misc/upgrading.md

eddyystop commented 5 years ago

"Luc - I can’t simply store the locale on the user record as some things I need the locale for happen with an unauthenticated user - an example is a “forgot password” email - the email’s language needs to match the language set on the iPhone when the request was dispatched."

eddyystop commented 5 years ago

New status report.

I will no longer keep extending the migration document. Its gotten too long, and I think the best way to migrate will be to read the articles and slide that code into your app.

eddyystop commented 5 years ago

https://www.eff.org/deeplinks/2016/12/how-enable-two-factor-authentication-facebook

claustres commented 5 years ago

which articles on Medium are you referring to ?

eddyystop commented 5 years ago

The ones mentioed here https://github.com/feathers-plus/authentication-local-management/issues/8

eddyystop commented 5 years ago

Candidate new features:

(0) Consider a-l-m making internal calls using _get, _find, _patch. These methods are standard (I believe) in all core adapters except feathers-memory. They will be made part of the adapter standard in v3. Before and after hooks are not run with them. Using them would prevent interference with configured hooks; we get and we write directly to the DB.

(1) Add support for 2FA. This would additionally require a custom authenticator. This should be a separate repo and I wouldn't be implementing it.

(2a) Maintain time user last logged on as well as the time before that. This allows the UI to display a "You last logged on at xxx" message. Should IP and some device indication also be kept? A longer chronological list of logon times, IP and device is more properly a logger responsibility.

(2b) Send a verification notification if last logon was too long ago. This is conceptually similar to 2FA.

(3a) For each user maintain a list of previous { hashedPassword, dateCreated } and prevent old passwords from being reused. Do we keep this list in the users service for simplicity or in a related service? May be best to implement as a plugin to a-l-m.

(3b) A new sendResetPwdHistorical request for the client. Its similar to sendResetPwd but requires one of the previous hashedPasswords.

(4a) Maintain a list of IPs and/or devices that have logged on. In users service or in a related service? May be best to implement as a plugin to a-l-m.

(4b) Email/SMS a short token when a new IP/device logs into. This is conceptually similar to 2FA.

(5) Invitation request allowing a user to invite another to join

(6) Gmail sends a 'signing in from another device?' SMS.

Potential features a-l-m won't be implementing.

Misc

(*) Look at

(*) Look at

(***) Look at

eddyystop commented 5 years ago

@claustres @beeplin @daffl @Marshall @j2L4e your comments on the above would be appreciated.

eddyystop commented 5 years ago

The features feathers-authentication-management provided, reimplemented here in a-l-m, were confusing enough to people. Articles were written (https://github.com/feathers-plus/authentication-local-management/issues/8) explaining its integration and they received a lot of applause.

I've spent considerable time preparing diagrams to help explain a-l-m. https://www.lucidchart.com/invitations/accept/f4ebc4fd-3297-47e4-8568-eb13778c535c

I think a-l-m is a handful already, and we now want to add more complex features to it. I think additional features should be implemented as plugins. That would keep a-l-m lean conceptually and users would not feel the need to learn everything at once.

The plugins could be released in a more controlled fashion.

NickBolles commented 5 years ago

@eddyystop those diagrams are awesome! It would be beneficial I think to have documentation ( maybe an article makes more sens though) that walks through each part of the diagrams to explains how it is done with code. I think a confusing part is what almost handles and what it doesn't and that an approach like that would help. Great job!

claustres commented 5 years ago

As usual you did a great work and diagrams are often better than written doc, with articles this could be sufficient IMHO. About the features:

  1. I don't think it's a good idea to bypass the service, see my previous comment. As an example why it could lead to unexpected behaviour as well see what I learned the hard way: https://github.com/feathersjs/feathers/issues/1049. Often hidden states do exist in apps and are managed through hooks or events.
  2. Hard to provide a generic module here, could only be a thin wrapper over SMS or push services like SNS since token generation and verification is already part of a-l-m. Not worth it IMHO.
  3. & 4. Are pretty related to devices management. We implemented something like that in a dedicated service for mobiles: https://github.com/kalisio/kNotify/blob/master/src/services/devices/devices.service.js. This is often linked to push notifications and mobile frameworks, which really depends on your implementation. So yes separated plugins are the best choice since otherwise you will have to make too much opiniated choices for a core module
  4. Easily implemented with a hook, maybe not worth a plugin, see https://blog.feathersjs.com/feathersjs-in-production-password-policy-and-rate-limiting-32c9874dc563. The difficulty here is to handle i18n for most common passwords. 3b could be also implemented as answer to secret questions since we often do not recall the history. Not sure if usefull with 2FA.
  5. Seems to me the most similar feature than existing ones: also relies on email sending with temporary token to be checked, should be easy to implement. The difficult part here is where to store the token if we only create the user when he responds ? On our side we have chosen to create the user on invitation and remove his account if he did not respond in eg 48 hours.
eddyystop commented 5 years ago

@NickBolles Those diagrams are intended to be used in a series of articles. The potential enhancements would introduce new ways of authenticating, and additional diagrams would be needed for those.

@claustres

  1. People could customize the default service calls to use _find if needed I guess.

  2. 2FA would need tfaExpires, tfatoken, tfaShortToken, its own command and the notifier should know what its sending.

  3. & 4. Code! Thanks.

  4. Yes, it could be written outside a-l-m, however that may result in questions. My philosophy is to put more work up front rather than continuously deal with questions.

lwhiteley commented 5 years ago

@eddyystop Are you looking at adding in the possibility to manage multiple password/hashed fields?

Use case: an account system that has both a password and a pin.

Adding an override somewhere here should do the trick

options.passwordField = data.passwordField || options.passwordField;

The information about which password field being changed/reset can be passed to the notifier as well.

Relevant actions:

Let me know if that makes sense or can be supported.

eddyystop commented 5 years ago

@lwhiteley , I am not clear about the use case. Are you asking if a person can log on using email/phone as an account, plus either passord or pin? If so, that is handled via a customized authenticator in @feathersjs/authentication.

a-l-m does not handle the actual authentication, it manages some of the fields on the users record. For your case, I would first try making the pin field as an identity fields and change it using the identityChange action. The identityChange action sends a verification token and waits for a confirmation before updating the field.

A reset could not be done on the pin. A reset on the password followed by an identityChange would be the suggestion.

xendarboh commented 5 years ago

Potential features a-l-m won't be implementing.

Email HTML templates with i18n. This should be a separate repo and it should support HTML templates, i18n and file attachment. What existing options are there to wrap in an adapter? (*) (**)

Push notification using SMS, WhatsApp. This should be a separate repo. What existing options are there to wrap in an adapter? (***)

notifme-sdk is a notification abstraction for providers that include emails | SMS | pushes | webpushes | slack (I don't see WhatsApp).

Notification Catcher is a web interface for viewing and testing notifications during development.

notifme-template plugin features include: Easily implement i18n/l10n for all your notifications

feathers-notifme: Feathers notification service using notifme-sdk

eddyystop commented 5 years ago

@xendarboh That looks very interesting.

NickBolles commented 5 years ago

@xendarboh @eddyystop Hopefully this info adds some more info to help with implementing emails/notifications. I'm using notifme-sdk and notification catcher and it works awesome (at least during dev). I haven't dealt with I18N yet, and it seems to me that a feathers service is overkill, you can just setup notifme in it's own file and import it.

Here's my auth management notifier and notifme setup. Keep in mind it's a little bit customized to my client setup, and most of it is typescript types... https://gist.github.com/NickBolles/1bd3331c02feadcd34fd2042ca73b32c

eddyystop commented 5 years ago

Services have hooks which support authententication, etc., allowing the client to call the notifier. I also like the idea of wrapping what we can in services, even in thin wrappers, to reduce the cognative load.

It would be appreciated if someone wrote non-generated code showing how each of the features would be coded for. That would be the target for the generator, as well as a source from which to decide the prompts for cli+.

I did not see in my brief review of the repo how notifier would be coded to send one email of several. a-l-m needs that option.

eddyystop commented 5 years ago

Status update

Scaffolding to implement plugin support added as @feathers-plus/plugin-scaffolding.

Initial plugin support added to a-l-m.

feathers-plus/commons has been introduced as a respository of cross-cutting utilities.

Lesson learned

// Prefer this
const [result] = await foo();
// to
const result = (await foo())[0];

Its too easy to miscode the latter and the consequence could be extremely confusing. Tracking down one such bug took longer than writing and using the plugin support.

xendarboh commented 5 years ago
// Prefer this
const [result] = await foo();
// to
const result = (await foo())[0];

nice! reminds me of the await to pattern for both result and error that I've seen recently in a few places. It also presents a way to use async/await without try/catch. await-to-js is pretty popular for a small one-function package.

const [err, result] = await to(bar());
eddyystop commented 5 years ago

I'm on G4 in a jungle but my understanding is that an await throwing in a function (without try-catch) will cause the function to throw. I don't see wrapping every await in a try-catch unless some param is needed for a user error message. And such cases are usually at the top level function.

eddyystop commented 5 years ago

I'm looking for critiques and comments about this technique and this article https://blog.feathersjs.com/use-plugins-to-publish-incredibly-flexible-code-a55e8faf27a8

eddyystop commented 5 years ago

Finished conversion to plugins.It took longer than anticipated. https://blog.feathersjs.com/use-plugins-to-publish-incredibly-flexible-code-a55e8faf27a8

eddyystop commented 5 years ago

I believe we are finished with converting feathers-authentication-management and can start implementing new features. COMMENTS ARE INVITED.

eddyystop commented 5 years ago

[Completed. Awaiting comments.]

(a) Send an invitation for a user to sign-up.

eddyystop commented 5 years ago

@claustres ^^^

eddyystop commented 5 years ago

[Completed. Awaiting comments.]

(b) There has been discussion whether 2FA is useful or not. However the same underlying process can be used for 2FA, a sign-in after too long an interval, a sign-in for a new device, etc. I feel its worthwhile to implement enabling capabilities for these features.

[In design. Awaiting comments.]

Some new fields may be introduced to support some of these mfa usages:

??? Is it sensible for a-l-m to keep the following as it is authentication itself that would set them.???

Do we introduce a changePasswordWithHistory command which prevents use of a password that's in the historical stack of passwords? (This would implemented seperately from mfa development.) changePasswordWithHistory would maintain the stack. Is it correct the existing changePassword does not maintain that stack?

The design @marshallswain suggests for 2FA is as follows:

eddyystop commented 5 years ago

Reserved.

eddyystop commented 5 years ago

[In design. Awaiting comments.]

(c) Support multiple password-like fields such as password & pin#, as suggested by @lwhiteley.

eddyystop commented 5 years ago

Reserved.

claustres commented 5 years ago

I took some time to read your article about plugins and it seems to me simple, elegant and flexible, so far so good.

About invitation the process seems fine to me, it is more secure than the one we created awaiting enhancement in a-l-m: no password generated upfront. Using plugins or options it would be easy to customize things like to add the sponsor who invited the user or whatever.

About 2FA I agree it is useful for new device singin so MFA code type is probably a great thing. We should take care to prevent concurrency here so that if the MFA started for a given code is not closed you cannot start a new one I guess ? About all your questions I think this could be implemented by plugins to keep the module lighter: password history, signin count/date, device/IP management, etc. But all of this makes sense.

Thanks for the damn big work done, now it will be time for us to start migrating our code base and provide feedback, I am afraid it will not be before a couple of months or so...

eddyystop commented 5 years ago

@claustres

(1) " if the MFA started for a given code is not closed you cannot start a new one I guess ?" I think you could get stuck with this.

(2) I'm not sure what the advantage would be keeping the server code light. I am intending to break the plugins in 2 sets, doing a conditional require for the second set. This has more to do with policy than code size.

@lwhiteley @claustres

My issue at the moment is how to handle multiple passwords as mentioned by lwhiteley.

claustres commented 5 years ago

Can't see why we would stop at 2 passwords, if we start supporting more password yes go with an array.

I wonder if handling multiple passwords could be managed by a plugin that simply "overrides" the standard call eg https://github.com/feathers-plus/authentication-local-management/blob/master/src/password-change.js#L48 by looking in options as an array instead of a single value. In this case what we need is a way to send to plugins additional input data from the frontend like the name of the password field name or index to be changed. For instance in a reserved sanatized field like clientData, then there is no need to create a new set of commands.

What do you think ?

eddyystop commented 5 years ago

Overridding the standard plugin is still a new command, as it would need a different signature. Using the same plugin name could, I feel, just make it more confusing.

There is also the issue that the standard hashPassword() hook couldn't be used.

On the other hand, having a separate plugin, we now have 'password' plus an array of extra passwords.

claustres commented 5 years ago

The hashPassword() takes as an option the field to be used so combined with appropriate iff on the additional input data from client indicating which field to be changed this can be easily setup I guess or do I miss something ?

I don't think same name will be confusing as long as the plugin managing password arrays also calls the standard plugin for single password so that the old behavior is kept. To avoid the cognitive load the plugin should be optional so that the single mode is still the default one.

eddyystop commented 5 years ago

You would need a hashPassword for each password, or write a custom one to loop through each one.

Your second suggestion makes sense. But I still feel drawn to the idea of separating out the "extra" passwords. I feel it's easier on people to explain a new addon feature than to undo a previous one. Lemme think on it.

eddyystop commented 5 years ago

@claustres

(1) The following make it more awkward to have a multi-password plugin which overrides the single-password one.

(2) Let's look at this from a completely different direction. What do we really want when we say we want additional passwords?

Am I missing something here or can additional passwords be implemented using

The "best" code is the code you don't have to write.

eddyystop commented 5 years ago

[In design. Awaiting comments.]

(d) Password strength hook

We might as well have a convenience hook which evaluates password strength. The Dropbox one ( https://github.com/dropbox/zxcvbn ) seems to be recommended. It uses an algorithmic approach rather than "at least ? chars with at least ? digits and ? capital letters". Its English centric unfortunately.

The hook's signature would be minimumPasswordStrength(level) where level is from 0, too guessable, to 4, very unguessable.