Skip to content

Model#initialize drops every false value — booleans are silently lost on requests and responses #800

Description

@lacma2011

Describe the problem

Auth0::Internal::Types::Model#initialize resolves each declared field with ||, so a legitimate false is treated as absent and never reaches @data. Since #to_h only emits keys present in @data, every boolean whose value is false silently disappears — on requests and responses alike.

Requests: users.update(id:, blocked: false) builds an empty PATCH body, and Auth0 rejects it with 400 Payload validation error: 'None of the valid schemas were met'. Unblocking a user is not possible through the SDK.

Responses: a user who genuinely is "email_verified": false deserializes to nil, indistinguishable from "the API did not return this field". This one is quiet — no exception, just a wrong value.

Reproduction

require "auth0/version"   # still needed on 6.2.0, see #786
require "auth0"

k = Auth0::Users::Types::UpdateUserRequestContent

k.new(id: "auth0|abc", blocked: false, email_verified: false).to_h
# => {"id" => "auth0|abc"}                       # both booleans dropped
k.new(id: "auth0|abc", blocked: true).to_h
# => {"id" => "auth0|abc", "blocked" => true}    # true survives

json = '{"user_id":"auth0|abc","email":"a@b.com","email_verified":false,"blocked":false}'
u = Auth0::Types::UpdateUserResponseContent.load(json)
u.email_verified  # => nil   (expected false)
u.blocked         # => nil   (expected false)
u.to_h            # => {"user_id" => "auth0|abc", "email" => "a@b.com"}

Users::Client#update does request_data.except("id"), so the first case sends PATCH /api/v2/users/auth0|abc with a literal {} body.

Expected behaviour

false round-trips like any other value: to_h includes the key and the reader returns false.

Cause

lib/auth0/internal/types/model.rb:

value = values.delete(field.api_name.to_sym) || values.delete(field.api_name) || values.delete(field_name)

field_value = value || (if field.literal?
                          field.value
                        elsif field.default
                          field.default
                        end)

Both || chains discard false. Presence has to be decided with Hash#key?, and the literal/default fallback should apply only when the value is genuinely nil:

key = [field.api_name.to_sym, field.api_name, field_name].find { |k| values.key?(k) }
value = key.nil? ? nil : values.delete(key)

field_value =
  if !value.nil?
    value
  elsif field.literal?
    field.value
  else
    field.default
  end

(Also note the elsif field.default guard is itself truthiness-based, so a field declared with default: false can never fall back to its default either.)

Because this lives in the shared base model it affects every boolean in the generated API surface — blocked, email_verified, verify_email, phone_verified, include_totals, and so on.

Environment

  • auth0 6.2.0lib/auth0/internal/types/model.rb is byte-identical in 6.0.0, 6.1.0 and 6.2.0 (md5 0908fa9ec6636316bc78688d3e7436b3), so all three are affected
  • Ruby 3.4

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions