Skip to content

Do not allow empty username and password when create user #30

Closed
freizl opened this Issue May 19, 2012 · 27 comments

7 participants

@freizl
freizl commented May 19, 2012

Turns out that Snap.Snaplet.Auth.Handlers.createUser allow empty username and password.
I think it is better to dis-allow it than ask API user to do that.

Thanks.

@ozataman
Snap Framework member
ozataman commented Aug 1, 2012

Good point. We leave validation to the API user, but we should certainly not allow blank username/password entries. We'll need to fix this.

@mightybyte
Snap Framework member

I'm not so sure about this. It seems to me like there might be a case for empty passwords. Maybe not for usernames though.

@gregorycollins
Snap Framework member

Definitely not usernames.

@Pedromdrp

The minimum password length is being ignored/not used.

@adinapoli
Snap Framework member

Hi guys, sorry for the OT. I would like to contribute, but the quantity of code is overwhelming. I've forked the project and created a (probably naive) patch for this issue, but I'm not sure which is the right branch to commit into: 0.10 perhaps? What are the guidelines? I would also like to know why there is no trace for the createUser function inside the test sources. Is it ever tested? Sorry for polluting the discussion but I was unable to find this information elsewhere (and btw your google groups seems to have attracted spammers :( )

Cheers,
Alfredo

@mightybyte
Snap Framework member

If the patch does not change the external API at all, then put the change on master and I'll merge it into 0.10 afterwards. The Haskell PVP generally governs our versioning policy.

@ozataman
Snap Framework member
@mightybyte
Snap Framework member

I second ozataman. In my mind the test suite is an underappreciated way to get familiar with Snap. This is why I included literate Haskell tutorials as part of the test suite for the new Heist functionality.

@adinapoli
Snap Framework member

It was on the cards to add tests for createUser, now even more :) From the little I've seen you test not only the pure code with Quickcheck / HUnit, but you also have sandboxes you use inside the test to simulate an entire "running" session of Snap, am I right? I'm referring to the code inside snap/test and the cabal-dev wizardry.

If I'm correct, I guess I should put createUser tests inside a new sandbox testing its behaviour, am I right?
Sorry for the newbie questions but as you already guessed this is my first "serious" project contribution :)

Cheers,
A.

@mightybyte
Snap Framework member

Both the createUser and saveUser will probably need new checks--both for a non-empty username and that the new password meets the minimum length requirements. We have a minPasswordLen field in the AuthManager data structure, but currently we're not using it anywhere. This is clearly an oversight on our part and probably a good reason to change the external API to make createUser, saveUser, and probably also registerUser return a Maybe or Either String. I'm leaning towards Either String AuthUser because we probably want to provide an error message indicating the reason for the failure. Maybe ozataman can chime in here with any other thoughts.

@adinapoli
Snap Framework member

Perfect, it sounds reasonable! I'll dive into the code tomorrow morning, and I'll keep you posted :)

@mightybyte mightybyte added a commit that referenced this issue Sep 16, 2012
@adinapoli adinapoli Fixed issues #30 and #38, and add test
Modified createUser to check for empty usernames.
Fixed content tags in project template.
1c8ad9b
@nurpax
nurpax commented Sep 25, 2012

Please see my comment 1c8ad9b#commitcomment-1899642 on error handling consistency. Is it ok to mix both exceptions (DuplicateLogin) and Either? I would argue that we shouldn't mix Either and exceptions. Throwing an exception also wouldn't break the existing API.

@mightybyte
Snap Framework member

I'd go with Either unless ozataman had a specific reason for exceptions.

@ozataman
Snap Framework member

My reason here for using exceptions rather than Either was that I had imagined it would be hard to ascertain all potential exceptions a backend may raise ahead of time. Some exceptions can certainly be defined at the Auth level, like DuplicateLogin, but others can certainly be backend specific - like "MySQL server has gone away".

Also, using Either means that we would be forcing the API user to handle exceptions explicitly everywhere they may happen. I originally went with exceptions to keep things a bit more lax. Admittedly, that approach tends to stuff things under the carpet.

I'd support using Either as long as the change is made pervasively in the API. It's definitely not OK to mix the two as that'd make for a very confusing API. Essentially, someone needs to go in and change every mention of "throw" to something that returns an Either type.

I'd also suggest that we collect all possible save/update exceptions under a single sum type to use with Either instead of a String. The BackendError seems like a good candidate here; we can rename it as appropriate and add cases for empty/insufficient passwords, etc. BackendError String case can cover the unknown cases.

Should we also wrap every other possible backend exception with a catch and stuff it into the BackendError String case? This way these Handlers can never raise an exception, which hopefully would simplify the API.

@mightybyte
Snap Framework member

Thanks for weighing in, Oz. I'm not categorically opposed to exceptions, but as I've said before I prefer Either or Maybe as a general rule. Switching all error conditions to Either BackendError a sounds good to me. I've already added a dependency on tekmo's awesome errors package, so we should definitely leverage that API to implement this stuff. It would be great if someone else could pick this up as I'm quite busy with finishing up Heist for the 0.10 release.

@ozataman
Snap Framework member
@adinapoli
Snap Framework member

With appropriate guidance I'll be happy to help :)

@adinapoli
Snap Framework member

Should we put the new sum type under src/Snaplet/Auth/Types.hs or under a new file, namely
src/Snaplet/Auth/Backends/Types.hs ?

The latter seems to provide more semantic cohesion, at least in my opinion.

@ozataman
Snap Framework member
@adinapoli
Snap Framework member

I began working on the refactoring. Consider this function, which has the old signature:

registerUser
  :: ByteString            -- ^ Login field
  -> ByteString            -- ^ Password field
  -> Handler b (AuthManager b) (Either String AuthUser)
registerUser lf pf = do
    l <- fmap decodeUtf8 <$> getParam lf
    p <- getParam pf
    case liftM2 (,) l p of
      Nothing         -> throw PasswordMissing
      Just (lgn, pwd) -> createUser lgn pwd

The refactoring should suggest registerUser to return a Either AuthFailure AuthUser, because the failure is at the "Auth" level. There are two problems, though:

a) registerUser throws a PasswordMissing exception, but this is against our new guideline; the problem can be fixed removing Exception and Error typeclasses from AuthFailure, and simply write the function like this:

registerUser
  :: ByteString            -- ^ Login field
  -> ByteString            -- ^ Password field
  -> Handler b (AuthManager b) (Either AuthFailure AuthUser)
registerUser lf pf = do
    l <- fmap decodeUtf8 <$> getParam lf
    p <- getParam pf
    case liftM2 (,) l p of
      Nothing         -> return $ Left PasswordMissing
      Just (lgn, pwd) -> createUser lgn pwd   --- Discordant exception type!

As you may notice, createUser has now the following signature:

createUser :: Text              -- ^ Username
           -> ByteString        -- ^ Password
           -> Handler b (AuthManager b) (Either BackendError AuthUser)

This is due to the fact createUser relies on the backend, which can fail, so it does make sense to return that type of error. What do you suggest? It seems like we can merge the two data types or at least provide a guideline here about how to redeem the question.

Sorry for the lengthy comment.

@mightybyte
Snap Framework member

I guess we should probably collapse BackendError and AuthFailure into a single type that we use everywhere. That gives us slightly less type safety, but seems better in terms of simplicity. Any objections Oz?

@ozataman
Snap Framework member
@adinapoli
Snap Framework member

Yep, collapsing was a thing I considered, so I'll stick with it!
@ozataman sure I can :)

@adinapoli
Snap Framework member

@ozataman Thinking more deeply about that I spot a nuisance: defining the show author that way will hardcode the error message to the English language, which is no good for site that needs internationalization.
I don't know the support that Snap has for internationalization, but we should definitely rely on it or make the mechanism scalable and easily adaptable to any context. What you reckon?

@mightybyte
Snap Framework member

That's why we are using Either AuthFailure a instead of Either String a. The latter would lock them in to a particular error message. A Show instance for AuthFailure doesn't lock them into anything. It just provides a nice sensible default. If they need internationalization, then they'll just define their own showFailure function.

@adinapoli
Snap Framework member

Yep, although I think we must be very careful not to call any show AuthFailure inside our Auth snaplet, therefore dooming foreign users to hack the internals in order to overwrite the behavior :)

ps. Just to be sure, have we choosen a type yet? Who will triumph between AuthFailure and BackendError ?

@mightybyte
Snap Framework member

Correct.

@adinapoli adinapoli added a commit to adinapoli/snap that referenced this issue Sep 30, 2012
@adinapoli adinapoli Type-fixing after the refactoring discussed in issue #30 9f2bf9f
@adinapoli adinapoli added a commit to adinapoli/snap that referenced this issue Sep 30, 2012
@adinapoli adinapoli Finished refactoring failures as discussed in issue #30 1844d15
@mightybyte mightybyte closed this Oct 8, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.