Search code examples
ruby-on-railsdevisejwtangular2-jwt

Rails 5.1: devise_token_auth returns 2 errors when only 1 should be found


Similar to this question for the regular devise gem, using devise_token_auth gem is showing the same result - validation errors appearing twice in the json response!?

From log:

Processing by DeviseTokenAuth::RegistrationsController#create as JSON
  Parameters: {"name"=>"Mickey Mouse", "email"=>"mickeymouse@gmail.com", "password"=>"[FILTERED]", "confirmPassword"=>"[FILTERED]", "confirm_success_url"=>"http://localhost:4200/register", "registration"=>{"name"=>"Mickey Mouse", "email"=>"mickeymouse@gmail.com", "password"=>"[FILTERED]", "confirmPassword"=>"[FILTERED]", "confirm_success_url"=>"http://localhost:4200/register"}}
Unpermitted parameters: :confirmPassword, :confirm_success_url, :registration
Unpermitted parameters: :confirmPassword, :confirm_success_url, :registration
   (0.2ms)  BEGIN
  User Exists (0.9ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "mickeymouse@gmail.com"], ["LIMIT", 1]]
   (0.8ms)  SELECT COUNT(*) FROM "users" WHERE "users"."provider" = $1 AND "users"."email" = $2  [["provider", "email"], ["email", "mickeymouse@gmail.com"]]
   (0.3ms)  ROLLBACK
Completed 422 Unprocessable Entity in 247ms (Views: 0.7ms | ActiveRecord: 6.9ms)

Notice that the unpermitted_parameters line is shown twice - which seems to indicate something odd (those lines don't show via Postman).

My user model has nothing extra from the standard guide so I definitely do not have 2 uniqueness or presence validations on my model, and examining the gem's source code it also does not appear to have that.

Here is the model:

class User < ActiveRecord::Base

  # Include default devise modules.
  devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :trackable, :validatable,
          :confirmable, :omniauthable
  include DeviseTokenAuth::Concerns::User
end

If I call this endpoint from Postman, I get the same result, here is the returned json:

{
    "status": "error",
    "data": {
        "id": null,
        "account_id": null,
        "provider": "email",
        "uid": "",
        "name": null,
        "nickname": null,
        "image": null,
        "email": "mickeymouse@gmail.com",
        "created_at": null,
        "updated_at": null
    },
    "errors": {
        "email": [
            "has already been taken",
            "has already been taken"
        ],
        "full_messages": [
            "Email has already been taken",
            "Email has already been taken"
        ]
    }
}

The Rails API is called from Angular2 using the angular2-token library, but that is clearly not the issue (give the result from Postman).

How can I either find the cause of this OR how can I monkey-patch the gem to remove the 2nd error?

UPDATE

If I remove :validatable from the model and put my own validation:

  validates_uniqueness_of :email

I get the same result, which is odd.


Solution

  • Edit: This issue has been fixed, just upgrade your gem! > v0.1.43.beta1


    I found the issue is reported on github.

    The workaround is to remove :validatable (email uniqueness will still fire) and then roll your own validation for password confirmation as shown in that issue (see the response there by stephanebruckert) and reproduced here:

    Quote: "validatable validates both the email and the password, which is why we shouldn't do any of those in models, otherwise it would be validated twice. In my case, only the email was validated twice, whereas I wasn't adding any extra validation in my model. So I ended up removing validatable and made my own validation on password and password_confirmation:"

      validates_presence_of :password, on: :create
      validates :password,
        length: { minimum: 8 },
        allow_blank: true
      validate  :password_complexity
      validates_confirmation_of :password
      validates_presence_of :password_confirmation, if: lambda {| u| u.password.present? }
    
      private
    
        def password_complexity
          return unless password
          if password.include?(username) || !password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)./)
            errors.add(:password, "Must include at least one lowercase letter, one uppercase letter and one digit")
          end
        end
    

    Although this does solve the duplicate email error, I now get a 'password_confirmation can't be blank error' ... which leads down a rabbit hole to discover this issue. You will need to read it all but basically, an api should not enforce the presence of password confirmation value (only needed in the form itself) :)