Class: Parse::User

Inherits:
Object show all
Includes:
MFA::UserExtension
Defined in:
lib/parse/model/classes/user.rb,
lib/parse/two_factor_auth/user_extension.rb,
lib/parse/stack/generators/templates/model_user.rb

Overview

Reopen User class to include MFA extension

Constant Summary

Constants inherited from Object

Object::BUILTIN_PARSE_CLASS_NAMES, Object::IDENTIFICATION_FIELDS, Object::VALID_ACL_POLICIES

Constants included from Core::Schema

Core::Schema::DEFAULT_PUBLIC_CLP, Core::Schema::SCHEMA_READONLY_CLASSES

Constants included from Core::Describe

Core::Describe::ALL_SECTIONS, Core::Describe::CORE_FIELD_KEYS, Core::Describe::LOCAL_SECTIONS, Core::Describe::NETWORK_SECTIONS

Constants included from Core::Indexing

Core::Indexing::INDEX_REGISTRY_MUTEX, Core::Indexing::MAX_INDEXES_PER_COLLECTION, Core::Indexing::PARSE_MANAGED_ARRAY_FIELDS, Core::Indexing::SENSITIVE_FIELDS

Constants included from Core::SearchIndexing

Core::SearchIndexing::ALLOWED_INDEX_TYPES, Core::SearchIndexing::INDEX_NAME_PATTERN

Constants included from Core::VectorSearchable

Core::VectorSearchable::VECTOR_VISIBILITY_MODES

Constants included from Core::Fetching

Core::Fetching::NON_SERIALIZABLE_IVARS

Constants included from Core::EmbedManaged

Core::EmbedManaged::WRITER_KEY

Constants included from Core::ParseReference

Core::ParseReference::OBJECT_ID_LENGTH, Core::ParseReference::SEPARATOR

Constants included from Core::FieldGuards

Core::FieldGuards::GUARD_MODES

Constants included from Properties

Properties::BASE, Properties::BASE_FIELD_MAP, Properties::BASE_KEYS, Properties::CORE_FIELDS, Properties::DELETE_OP, Properties::PROTECTED_INITIALIZE_KEYS, Properties::PROTECTED_MASS_ASSIGNMENT_KEYS, Properties::TYPES

Constants inherited from Pointer

Pointer::ATTRIBUTES, Pointer::OBJECT_ID_FORMAT

Constants inherited from Model

Model::CLASS_AUDIENCE, Model::CLASS_INSTALLATION, Model::CLASS_JOB_SCHEDULE, Model::CLASS_JOB_STATUS, Model::CLASS_PRODUCT, Model::CLASS_PUSH_STATUS, Model::CLASS_ROLE, Model::CLASS_SCHEMA, Model::CLASS_SESSION, Model::CLASS_USER, Model::ID, Model::KEY_CLASS_NAME, Model::KEY_CREATED_AT, Model::KEY_OBJECT_ID, Model::KEY_UPDATED_AT, Model::OBJECT_ID, Model::SYSTEM_CLASS_MAP, Model::TYPE_ACL, Model::TYPE_BYTES, Model::TYPE_DATE, Model::TYPE_FIELD, Model::TYPE_FILE, Model::TYPE_GEOPOINT, Model::TYPE_NUMBER, Model::TYPE_OBJECT, Model::TYPE_POINTER, Model::TYPE_POLYGON, Model::TYPE_RELATION

Instance Attribute Summary collapse

Attributes inherited from Object

#acl, #created_at, #id, #updated_at

Attributes inherited from Pointer

#id, #parse_class

Class Method Summary collapse

Instance Method Summary collapse

Methods included from MFA::UserExtension

#confirm_sms_mfa!, #disable_mfa!, #disable_mfa_admin!, #disable_mfa_master_key!, #login_with_mfa!, #mfa_enabled?, #mfa_provisioning_uri, #mfa_qr_code, #mfa_status, #setup_mfa!, #setup_sms_mfa!

Methods inherited from Object

#[], #[]=, #__type, #_resolve_acl_owner_id, #_resolve_default_acl, #acl_changed?, acl_owner_field, acl_policy, acl_policy_setting, #acl_was, #acl_will_change!, #after_create, #after_destroy, #after_save, #after_update, #after_validation, #apply_defaults!, #around_create, #around_destroy, #around_save, #around_update, #around_validation, #as_json, #autofetch_disabled?, #before_create, #before_destroy, #before_save, #before_update, #before_validation, build, #changed, #changed?, class_permissions, #clear_attribute_change!, #clear_changes!, #clear_partial_fetch_state!, default_acls, describe_access, #disable_autofetch!, #enable_autofetch!, #existed?, fetch_clp, #fetched?, #fetched_keys, #fetched_keys=, #field_was_fetched?, #filter_for_user, filter_results_for_user, #fully_fetched?, #has?, #has_selective_keys?, #hybrid_ranks, #hybrid_score, #initialize, #keys, master_only_class!, #nested_fetched_keys, #nested_fetched_keys=, #nested_keys_for, #new?, #parse_class, #partially_fetched?, #persisted?, pointer, #pretty, private_acl!, #reload!, roles_for_user, #rollback!, #run_after_create_callbacks, #run_after_delete_callbacks, #run_after_save_callbacks, #run_before_create_callbacks, #run_before_save_callbacks, #schema, #search_highlights, #search_score, set_class_access, set_clp, set_default_acl, set_default_clp, set_read_user_fields, set_write_user_fields, #twin, unlistable_class!, update_clp!, #updates, #valid?, #validate!, #vector_score, wait_for, watch, webhook, webhook_function

Methods included from Core::Querying

#all, #all_as, #count, #count_distinct, #cursor, #distinct, #each, #find, #find_cached, #first, #first_as, #group_by, #group_by_date, #last_updated, #latest, #literal_where, #newest, #oldest, #pluralized_alias!, #query, #scope, #subscribe

Methods included from Core::Schema

#_default_class_level_permissions_for_upgrade, #auto_upgrade!, #create_schema, #fetch_schema, #reset_clp!, #schema, #update_schema

Methods included from Core::Describe

#describe

Methods included from Core::Indexing

#apply_indexes!, #indexes_plan, #mongo_geo_index, #mongo_index, #mongo_index_declarations, #mongo_relation_index, #unique_index_on

Methods included from Core::SearchIndexing

#apply_search_indexes!, #mongo_search_index, #mongo_search_index_declarations, #search_indexes_plan

Methods included from Core::VectorSearchable

#find_similar, #hybrid_search, #vector_visibility, #vectors_public_by_default?

Methods included from Agent::MetadataDSL

#agent_description, #agent_methods, #property_descriptions, #property_enum_descriptions

Methods included from Core::Actions

#_deleted?, #change_requests, #changes_applied!, #changes_payload, #destroy, #destroy_request, #op_add!, #op_add_relation!, #op_add_unique!, #op_destroy!, #op_increment!, #op_remove!, #op_remove_relation!, #operate_field!, #prepare_save!, #relation_change_operations, #save, #save!, #set_attributes!, #update, #update!, #update_relations, #uri_path

Methods included from Core::Fetching

#autofetch!, #fetch, #fetch!, #fetch_cache!, #fetch_json, #fetch_object, #prepare_for_dirty_tracking!

Methods included from Core::EmbedManaged

#compute_embedding!

Methods included from Associations::HasMany

has_many, #relation_changes?, #relation_updates, #relations

Methods included from Associations::BelongsTo

belongs_to, #key?

Methods included from Associations::HasOne

has_one

Methods included from Core::ParseReference

format, generate_object_id, parse

Methods included from Core::FieldGuards

#apply_field_guards!

Methods included from Properties

#attribute_changes?, #attribute_updates, #attributes, #attributes=, #field_map, #fields, #format_operation, #format_value

Methods inherited from Pointer

#==, #[], #[]=, #__type, #attributes, #className, #fetch, #fetch_cache!, #fetch_json, #fetch_object, #fetched?, #hash, #initialize, #json_hash, #method_missing, #pointer, #pointer?, #present?, #respond_to_missing?, #search_highlights, #search_score, #sig, #vector_score

Methods inherited from Model

#dirty?, find_class, same_parse_class?

Methods included from Client::Connectable

#client

Constructor Details

This class inherits a constructor from Parse::Object

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Parse::Pointer

Instance Attribute Details

#active_sessionsArray<Parse::Session>

A has_many relationship to all Session instances for this user. This will query the _Session collection for all sessions which have this user in it's user column.

Returns:

Version:

  • 1.7.1



279
# File 'lib/parse/model/classes/user.rb', line 279

has_many :active_sessions, as: :session

#auth_dataHash

The auth data for this Parse::User. Depending on how this user is authenticated or logged in, the contents may be different, especially if you are using another third-party authentication mechanism like Facebook/Twitter.

Returns:

  • (Hash)

    Auth data hash object.



244
# File 'lib/parse/model/classes/user.rb', line 244

property :auth_data, :object

#emailString

Emails are optional in Parse, but if set, they must be unique.

Returns:

  • (String)

    The email field.



249
# File 'lib/parse/model/classes/user.rb', line 249

property :email

#email_verifiedBoolean

Whether this user's email address has been verified. Set by Parse Server when the user follows the verification link delivered by the email adapter, and applied to the in-memory object by #signup! / signup-on-save when the server includes it in the signup response (see +SIGNUP_RESPONSE_APPLY_KEYS+).

Returns:

  • (Boolean)


271
# File 'lib/parse/model/classes/user.rb', line 271

property :email_verified, :boolean

#installationsArray<Parse::Installation>

A has_many query-form association resolving to all Installation records whose user pointer is this user. Useful for targeted push — e.g. sending a notification to every device the user is signed into. This is a query (no column is stored on _User); each access issues a find against _Installation for where(user: self).

Requires a master-key client. Parse Server hardcodes _Installation find to master-key-only at the REST layer, so this association will return an empty array (or fail-closed depending on agent scope) under a session-token-only / sessionless client. The user pointer is also not a reliable owner identity — devices outlive sessions and can change users — see Installation for the full caveat list.



297
# File 'lib/parse/model/classes/user.rb', line 297

has_many :installations, as: :installation

#session_tokenString

Returns The session token if this user is logged in.

Returns:

  • (String)

    The session token if this user is logged in.



237
238
239
# File 'lib/parse/model/classes/user.rb', line 237

def session_token
  @session_token
end

#usernameString

All Parse users have a username and must be globally unique.

Returns:

  • (String)

    The user's username.



262
# File 'lib/parse/model/classes/user.rb', line 262

property :username

Class Method Details

.anonymous_signupUser

Create and log in a new anonymous user via the +authData.anonymous+ provider. The returned user instance has a +session_token+ and an objectId, and #anonymous? returns true. Later, after the user has chosen a username and password, upgrade the account in-place with #upgrade_anonymous!.

Parse Server requires the anonymous-provider payload to include a client-generated +id+; this helper produces one via +SecureRandom.uuid+ so callers don't have to hand-roll the +authData+ shape.

Returns:

  • (User)

    a freshly-created, logged-in anonymous user.

See Also:



1128
1129
1130
# File 'lib/parse/model/classes/user.rb', line 1128

def self.
  autologin_service(:anonymous, { id: SecureRandom.uuid })
end

.autologin_service(service_name, auth_data, body: {}) ⇒ User

Automatically and implicitly signup a user if it did not already exists and authenticates them (login) using third-party authentication data. May raise exceptions similar to create depending on what you provide the body parameter.

Parameters:

  • service_name (Symbol)

    the name of the service key (ex. :facebook)

  • auth_data (Hash)

    the specific service data to place in the user's auth-data for this service.

  • body (Hash) (defaults to: {})

    any additional User related fields or properties when signing up this User record.

Returns:

  • (User)

    a logged in user, or nil.

See Also:



1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
# File 'lib/parse/model/classes/user.rb', line 1103

def self.autologin_service(service_name, auth_data, body: {})
  # Trust-mark this call so {.assert_create_body_safe!} permits the
  # +authData+ that we are explicitly responsible for here. The
  # marker is consumed inside {.create} before forwarding to the
  # server.
  body = body.merge({
    authData: { service_name => auth_data },
    __parse_stack_trusted_authdata: true,
  })
  self.create(body)
end

.create(body, **opts) ⇒ User

Creates a new Parse::User given a hash that maps to the fields defined in your Parse::User collection.

Mass-assignment of +authData+/+auth_data+/+objectId+ is refused. If you intend to create-or-login a user via federated identity, use autologin_service or link_or_create_with_auth_data. Passing those keys directly bypasses the SDK's federated-identity wrapper and risks returning a victim's sessionToken to whoever submitted the request.

Parameters:

  • body (Hash)

    The hash containing the Parse::User fields. The field username and password are required.

  • opts (Hash)

    a customizable set of options

Options Hash (**opts):

  • :master_key (Boolean)

    Whether the master key should be used for this request.

Returns:

  • (User)

    Returns a successfully created Parse::User.

Raises:



1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
# File 'lib/parse/model/classes/user.rb', line 1028

def self.create(body, **opts)
  # Consume and clear the SDK-internal trust marker before validation
  # or wire transit. This prevents trusted-authdata flag smuggling
  # through callers that copy hashes from a request parameter.
  trusted = body.is_a?(Hash) ? (body.delete(:__parse_stack_trusted_authdata) ||
                                body.delete("__parse_stack_trusted_authdata")) : false
  assert_create_body_safe!(body) unless trusted
  strip_server_controlled_keys!(body)
  response = client.create_user(body, **opts)
  if response.success?
    body.delete :password # clear password before merging
    # Self-fetch trust: the response.result describes the user we
    # just created, so any returned authData IS that user's own
    # federated-identity payload — allow it through the hydration
    # strip in {#apply_attributes!}.
    return with_authdata_trust { Parse::User.build body.merge(response.result) }
  end

  case response.code
  when Parse::Response::ERROR_USERNAME_MISSING
    raise Parse::Error::UsernameMissingError, response
  when Parse::Response::ERROR_PASSWORD_MISSING
    raise Parse::Error::PasswordMissingError, response
  when Parse::Response::ERROR_USERNAME_TAKEN
    raise Parse::Error::UsernameTakenError, response
  when Parse::Response::ERROR_EMAIL_TAKEN
    raise Parse::Error::EmailTakenError, response
  end
  raise Parse::Client::ResponseError, response
end

.login(username, password) ⇒ User

Login and return a Parse::User with this username/password combination.

Parameters:

  • username (String)

    the user's username

  • password (String)

    the user's password

Returns:

  • (User)

    a logged in user for the provided username. Returns nil otherwise.

See Also:



1149
1150
1151
1152
1153
1154
1155
# File 'lib/parse/model/classes/user.rb', line 1149

def self.(username, password)
  response = client.(username.to_s, password.to_s)
  return nil unless response.success?
  # Self-fetch trust: the login response IS the authenticating user;
  # any returned authData belongs to them.
  with_authdata_trust { Parse::User.build(response.result) }
end

.login!(username, password) ⇒ User

Login and return a Parse::User with this username/password combination, raising on failure instead of returning nil. Mirrors the find_by_username! / find! conventions: callers who treat an unsuccessful login as an exceptional condition shouldn't have to build their own raise if .nil? boilerplate around every call site.

Parameters:

  • username (String)

    the user's username.

  • password (String)

    the user's password.

Returns:

  • (User)

    the logged-in user.

Raises:

  • (Parse::Error::AuthenticationError)

    when Parse Server rejects the credentials, the request is rate-limited at the server, or the response is otherwise unsuccessful.

See Also:



1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
# File 'lib/parse/model/classes/user.rb', line 1170

def self.login!(username, password)
  response = client.(username.to_s, password.to_s)
  if response.success?
    # Self-fetch trust: see {.login}.
    with_authdata_trust { Parse::User.build(response.result) }
  else
    case response.code
    when Parse::Response::ERROR_EMAIL_NOT_FOUND
      # Parse Server throws code 205 (EMAIL_NOT_FOUND) when
      # +preventLoginWithUnverifiedEmail+ is set and the account's email
      # address has not yet been verified. Raise the typed error so callers
      # can direct the user to verify their inbox without catching every
      # AuthenticationError.
      raise Parse::Error::EmailNotVerifiedError,
            "Parse::User.login! failed for #{username.inspect}: " \
            "email address is not verified (code=205)"
    else
      raise Parse::Error::AuthenticationError,
            "Parse::User.login! failed for #{username.inspect}: " \
            "#{response.error || "HTTP #{response.http_status}"} (code=#{response.code.inspect})"
    end
  end
end

.master_only_fields(*fields) ⇒ Array<Symbol>

Hide one or more fields from query/get responses for all non-master callers, including the owning user themselves. Useful for admin-only metadata living on _User (e.g. internal scoring, moderation notes).

Requires Parse Server option protectedFieldsOwnerExempt: false. With the default true, the owning user still sees these fields on their own row.

Examples:

class Parse::User
  property :my_opinion_of_them, :string
  master_only_fields :my_opinion_of_them
end

Parameters:

  • fields (Array<Symbol,String>)

    field names. Use snake_case Ruby property names; they're auto-converted to camelCase.

Returns:

  • (Array<Symbol>)

    full master-only field list after this call.



404
405
406
407
408
409
410
# File 'lib/parse/model/classes/user.rb', line 404

def master_only_fields(*fields)
  @master_only_fields ||= []
  @master_only_fields = (@master_only_fields + fields.flatten.map(&:to_sym)).uniq
  _warn_about_owner_exempt_prereq!
  _rebuild_user_protected_fields!
  @master_only_fields.dup
end

.protect_fields(pattern, fields) ⇒ Object

Override Object.protect_fields on _User so that ad-hoc uses (i.e. not through master_only_fields / self_visible_fields) emit a one-time advisory pointing at the higher-level helpers and the protectedFieldsOwnerExempt flag. The behavior is otherwise unchanged.



446
447
448
449
# File 'lib/parse/model/classes/user.rb', line 446

def protect_fields(pattern, fields)
  _warn_about_user_protect_fields! unless @_user_field_dsl_active
  super
end

.request_email_verification(email) ⇒ Boolean

Request that Parse Server (re)send the email-address verification email for a registered, not-yet-verified user. The server must have an email adapter and verifyUserEmails enabled.

Examples:

# pass a user object
Parse::User.request_email_verification(user)
# or an email
Parse::User.request_email_verification("user@example.com")

Parameters:

Returns:

  • (Boolean)

    True/false if the request was accepted.

Raises:

  • (Parse::Error::ServiceUnavailableError)

    if Parse Server returns a 500/503 (e.g. no emailAdapter / verifyUserEmails disabled). Callers that branch on the Boolean should rescue this.



1228
1229
1230
1231
1232
1233
# File 'lib/parse/model/classes/user.rb', line 1228

def self.request_email_verification(email)
  email = email.email if email.is_a?(Parse::User)
  return false if email.blank?
  response = client.request_email_verification(email)
  response.success?
end

.request_password_reset(email) ⇒ Boolean

Request a password reset for a registered email.

Examples:

user = Parse::User.first

# pass a user object
Parse::User.request_password_reset user
# or email
Parse::User.request_password_reset("user@example.com")

Parameters:

  • email (String)

    The user's email address.

Returns:

  • (Boolean)

    True/false if successful.

Raises:

  • (Parse::Error::ServiceUnavailableError)

    if Parse Server returns a 500/503 (e.g. no emailAdapter / publicServerURL configured). Callers that branch on the Boolean should rescue this; if request_password_reset(email) is not exception-safe on a misconfigured server.



1208
1209
1210
1211
1212
1213
# File 'lib/parse/model/classes/user.rb', line 1208

def self.request_password_reset(email)
  email = email.email if email.is_a?(Parse::User)
  return false if email.blank?
  response = client.request_password_reset(email)
  response.success?
end

.self_visible_fields(*fields, via: :self) ⇒ Array<Symbol>

Hide one or more fields from public/role/other-user callers, but allow the owning user to see them. Useful for private profile data that belongs to the user (e.g. preferences, private notes).

Requires:

  • Parse Server option protectedFieldsOwnerExempt: false.
  • A self-pointer field on _User (named via via:, default :self) that is set to the row's own pointer by a beforeSave('_User') Cloud Code trigger.

Examples:

class Parse::User
  property :favorite_color, :string
  self_visible_fields :favorite_color, via: :self
end

Parameters:

  • fields (Array<Symbol,String>)

    field names. snake_case OK.

  • via (Symbol, String) (defaults to: :self)

    name of the self-pointer field on _User (default :self).

Returns:



431
432
433
434
435
436
437
438
439
# File 'lib/parse/model/classes/user.rb', line 431

def self_visible_fields(*fields, via: :self)
  @self_visible_fields ||= []
  @self_visible_fields = (@self_visible_fields + fields.flatten.map(&:to_sym)).uniq
  @self_pointer_field = via.to_sym
  _warn_about_owner_exempt_prereq!
  _warn_about_self_pointer_prereq!(via)
  _rebuild_user_protected_fields!
  @self_visible_fields.dup
end

.session(token, opts = {}) ⇒ User

Same as session! but returns nil if a user was not found or sesion token was invalid.

Returns:

  • (User)

    the user matching this active token, otherwise nil.

See Also:

  • #session!


1238
1239
1240
1241
1242
# File 'lib/parse/model/classes/user.rb', line 1238

def self.session(token, opts = {})
  self.session! token, opts
rescue Parse::Error::InvalidSessionTokenError
  nil
end

.session!(token, opts = {}) ⇒ User

Return a Parse::User for this active session token.

Returns:

  • (User)

    the user matching this active token

Raises:

  • (InvalidSessionTokenError)

    Invalid session token.

  • (ArgumentError)

    when opts smuggles a conflicting :session_token key — the positional token argument is the only source of truth; rejecting the kwarg prevents a silent override that would authenticate as a different user.

See Also:



1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
# File 'lib/parse/model/classes/user.rb', line 1252

def self.session!(token, opts = {})
  if opts.is_a?(Hash) && (opts.key?(:session_token) || opts.key?("session_token"))
    raise ArgumentError,
          "Parse::User.session! takes the session token as its positional " \
          "argument; do not also pass it via opts[:session_token]"
  end
  # support Parse::Session objects
  token = token.session_token if token.respond_to?(:session_token)
  response = client.current_user(token, **opts)
  return nil unless response.success?
  # Self-fetch trust: `/users/me` returns the row owned by the
  # supplied session token, so authData here is that user's own.
  with_authdata_trust { Parse::User.build(response.result) }
end

.signup(username, password, email = nil, body: {}) ⇒ Object

This method will signup a new user using the parameters below. The required fields to create a user in Parse is the username and password fields. The email field is optional. Both username and email (if provided), must be unique. At a minimum, it is recommended you perform a query using the supplied username first to verify do not already have an account with that username. This method will raise all the exceptions from the similar create method.

See Also:



1138
1139
1140
1141
1142
# File 'lib/parse/model/classes/user.rb', line 1138

def self.(username, password, email = nil, body: {})
  body = body.merge({ username: username, password: password })
  body[:email] = email if email.present?
  self.create(body)
end

.signup_body_self_only_acl_safe?(body) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

True when the signup-body objectId and ACL together describe the safe self-only ownership pattern that Object.acl_policy produces under owner: :self: the body has a client-assigned objectId matching the Parse-id format, and the ACL has exactly one entry granting read+write to that same objectId. Any deviation — multiple keys, a non-self key, a * (public) entry, a role: entry, missing or extra permissions — fails the check and the strip-everything fallback in #signup_create / #signup! runs as before.

Parameters:

  • body (Hash)

    signup request body, with symbol or string keys.

Returns:

  • (Boolean)


1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
# File 'lib/parse/model/classes/user.rb', line 1549

def self.(body)
  return false unless body.is_a?(Hash)
  oid = body[:objectId] || body["objectId"]
  acl = body[:ACL] || body["ACL"]
  return false unless oid.is_a?(String) && oid.match?(PARSE_OBJECT_ID_FORMAT)
  return false unless acl.is_a?(Hash) && acl.size == 1
  perms = acl[oid] || acl[oid.to_s]
  return false unless perms.is_a?(Hash)
  normalized = perms.transform_keys(&:to_s)
  normalized == { "read" => true, "write" => true }
end

Instance Method Details

#acl_roles(max_depth: 10, master: false, as: nil) ⇒ Set<String>

Return the transitive upward closure of role names this user inherits permissions from.

Authorization

The role graph is privileged data: Parse Server's _Role class ships with acl_policy :private precisely so anonymous clients cannot enumerate role memberships. This method therefore routes through the mongo-direct fast path under an EXPLICIT authorization scope.

By default, as: is set to self — the user instance itself, meaning "I (this user) am asking about my own roles". The scope is resolved via ACLScope and CLP is enforced against _Role: the call succeeds iff the user's permission set (["*", user.id, "role:..."]) is permitted to find on _Role. Under Parse Server's default _Role CLP (master-only, which Role's acl_policy :private does not change), the user's scope is NOT permitted, so this call raises CLPScope::Denied. Apps that have explicitly opened _Role CLP for authenticated users (e.g. find: { requiresAuthentication: true }) will have the call succeed.

Callers performing privileged work (computing ACL permission sets, e.g. server-side filters) should pass master: true to bypass the CLP check.

Breaking change: Previously this method bypassed the authorization check entirely (callers could construct a Parse::User with any objectId via Parse::User.new.tap { |u| u.id = victim_id } and enumerate the victim's roles). The new contract is explicit-auth-required; use master: true for the previous behavior.

Examples:

# User reading their own roles (subject to _Role CLP):
permission_set = (["*", user.id] + user.acl_roles.map { |n| "role:#{n}" }).uniq
# Admin/SDK-internal code building ACL filters:
permission_set = (["*", user.id] + user.acl_roles(master: true).map { |n| "role:#{n}" }).uniq

Parameters:

  • max_depth (Integer) (defaults to: 10)

    maximum BFS depth (default: 10).

  • master (Boolean) (defaults to: false)

    when +true+, bypass _Role CLP and run the role-graph lookup under master mode. Use for ACL-building code paths inside the SDK or in admin tooling.

  • as (Parse::User, Parse::Pointer, nil) (defaults to: nil)

    caller-scope. When nil, defaults to self (the user-asking-about-their-own-roles case). Pass a different user to ask "what would this caller see when introspecting this user's roles?"; the scope's permission set is checked against _Role CLP.

Returns:

  • (Set<String>)

    role names (no +role:+ prefix). Empty set when the user has no objectId yet or holds no roles.

Raises:



1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
# File 'lib/parse/model/classes/user.rb', line 1492

def acl_roles(max_depth: 10, master: false, as: nil)
  return Set.new unless id.is_a?(String) && !id.empty?
  # Default `as:` to self so the common "user reading their own
  # roles" case works without ceremony when _Role CLP permits the
  # user. The CLP check + scope resolution happens inside
  # Parse::Role.all_for_user → Parse::MongoDB.role_names_for_user.
  effective_as = as.nil? && master != true ? self : as
  Parse::Role.all_for_user(
    self, max_depth: max_depth, master: master, as: effective_as,
  )
end

#active_session_countInteger

Get the count of active (non-expired) sessions for this user. Requires an authenticated session (see #logout_all! for the rationale).

Examples:

count = user.active_session_count
puts "User is logged in on #{count} devices"

Returns:

  • (Integer)

    the number of active sessions

Raises:



1365
1366
1367
1368
1369
1370
1371
# File 'lib/parse/model/classes/user.rb', line 1365

def active_session_count
  return 0 unless id.present?
  require_self_session!("active_session_count")
  Parse.with_session(@session_token) do
    Parse::Session.active_count_for_user(self)
  end
end

#anonymous?Boolean

Returns true if this user is anonymous (i.e. created via the +authData.anonymous+ provider rather than via signup with a username/password or a real OAuth provider).

Returns:

  • (Boolean)

    true if this user is anonymous (i.e. created via the +authData.anonymous+ provider rather than via signup with a username/password or a real OAuth provider).



612
613
614
# File 'lib/parse/model/classes/user.rb', line 612

def anonymous?
  !anonymous_id.nil?
end

#anonymous_idString

Returns the anonymous identifier only if this user is anonymous.

Returns:

  • (String)

    The anonymous identifier for this anonymous user.

See Also:



619
620
621
# File 'lib/parse/model/classes/user.rb', line 619

def anonymous_id
  auth_data["anonymous"]["id"] if auth_data.present? && auth_data["anonymous"].is_a?(Hash)
end

#any_session!String

If the current session token for this instance is nil, this method finds the most recent active Parse::Session token for this user and applies it to the instance. The user instance will now be authenticated and logged in with the selected session token. Useful if you need to call save or destroy methods on behalf of a logged in user.

Returns:

  • (String)

    The session token or nil if no session was found for this user.



1297
1298
1299
1300
1301
1302
1303
# File 'lib/parse/model/classes/user.rb', line 1297

def any_session!
  unless @session_token.present?
    _active_session = active_sessions(restricted: false, order: :updated_at.desc).first
    self.session_token = _active_session.session_token if _active_session.present?
  end
  @session_token
end

Adds the third-party authentication data to for a given service.

Parameters:

  • service_name (Symbol)

    The name of the service (ex. :facebook)

  • data (Hash)

    The body of the OAuth data. Dependent on each service.

Raises:



627
628
629
630
631
# File 'lib/parse/model/classes/user.rb', line 627

def link_auth_data!(service_name, **data)
  response = client.set_service_auth_data(id, service_name, data)
  raise Parse::Client::ResponseError, response if response.error?
  self.class.with_authdata_trust { apply_attributes!(response.result) }
end

#logged_in?Boolean

Returns true if this user has a session token.

Returns:

  • (Boolean)

    true if this user has a session token.



744
745
746
# File 'lib/parse/model/classes/user.rb', line 744

def logged_in?
  self.session_token.present?
end

#login!(passwd = nil) ⇒ Boolean

Login and get a session token for this user.

Parameters:

  • passwd (String) (defaults to: nil)

    The password for this user.

Returns:

  • (Boolean)

    True/false if we received a valid session token.



886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
# File 'lib/parse/model/classes/user.rb', line 886

def login!(passwd = nil)
  self.password = passwd || self.password
  response = client.(username.to_s, password.to_s)
  if response.success?
    # Unlike signup, login's response is the canonical state of an
    # existing user, including any linked authData. Applying the
    # full response body here is intentional -- the server is
    # telling us what the account currently looks like. (Compare
    # signup, where we narrow to an allow-list because a brand-new
    # account has no legitimate authData to report.)
    self.class.with_authdata_trust { apply_attributes! response.result }
    # Drop the plaintext password from memory now that the login
    # has succeeded. Direct ivar assignment so the dirty tracker
    # doesn't record this clear as a pending change.
    @password = nil
    # Clear dirty state so a subsequent user.save! does not re-send
    # `password` (which Parse Server would treat as a password
    # change and use to revoke the session this login just issued).
    # See the matching note in #signup!.
    changes_applied!
    clear_partial_fetch_state!
  end
  self.session_token.present?
end

#logoutBoolean

Invalid the current session token for this logged in user.

Returns:

  • (Boolean)

    True/false if successful



913
914
915
916
917
918
919
920
# File 'lib/parse/model/classes/user.rb', line 913

def logout
  return true if self.session_token.blank?
  client.logout session_token
  self.session_token = nil
  true
rescue
  false
end

#logout_all!(keep_current: false) ⇒ Integer

Logout from all sessions, effectively signing out on all devices. Optionally keep the current session active.

Self-guard. Requires the user instance to carry a session token — i.e. to have been obtained via login/signup or attached via #session_token=. Without this, user.id = victim_id; user.logout_all! could revoke another user's sessions if the deployment has loose _Session write CLP. The guard fails closed in the SDK so the deployment's CLP isn't the only line of defense.

Examples:

# Logout from all devices
user.logout_all!

# Logout from all devices except current
user.logout_all!(keep_current: true)

Parameters:

  • keep_current (Boolean) (defaults to: false)

    if true, keeps the current session active (default: false)

Returns:

  • (Integer)

    the number of sessions revoked

Raises:



1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
# File 'lib/parse/model/classes/user.rb', line 1328

def logout_all!(keep_current: false)
  return 0 unless id.present?
  require_self_session!("logout_all!")
  current_token = @session_token
  # Self-scope the _Session query: in client mode the ambient client
  # has no auth, so the query must carry this user's session token to
  # be authorized against /classes/_Session. Master-key mode ignores
  # the ambient since the master key still wins.
  count = Parse.with_session(current_token) do
    # Always revoke the OTHER sessions first under the live token —
    # destroying the calling session mid-loop invalidates the token
    # and the remaining deletes 401. Then, if not keeping current,
    # close the calling session via the dedicated logout endpoint.
    n = Parse::Session.revoke_all_for_user(self, except: current_token)
    unless keep_current
      begin
        Parse.client.logout(current_token)
        n += 1
      rescue Parse::Error::InvalidSessionTokenError
        # The calling session was already gone (server-side TTL or
        # concurrent revoke). Idempotent: count what we destroyed.
      end
    end
    n
  end
  @session_token = nil unless keep_current
  @session = nil unless keep_current
  count
end

#multi_session?Boolean

Check if this user has multiple active sessions (logged in on multiple devices).

Examples:

if user.multi_session?
  puts "User is logged in on multiple devices"
end

Returns:

  • (Boolean)

    true if user has more than one active session



1395
1396
1397
# File 'lib/parse/model/classes/user.rb', line 1395

def multi_session?
  active_session_count > 1
end

#password=(value) ⇒ String

You may set a password for this user when you are creating them. Parse never returns a Parse::User's password when a record is fetched. Therefore, normally this getter is nil. While this API exists, it is recommended you use either the #login! or #signup! methods. (see #login!)

Returns:

  • (String)

    The password you set.



257
# File 'lib/parse/model/classes/user.rb', line 257

property :password

#request_email_verificationBoolean

Request that Parse Server (re)send this user's email-address verification email. The server must have an email adapter and verifyUserEmails enabled.

Returns:

  • (Boolean)

    true if the request was accepted, false otherwise.

Raises:

See Also:



766
767
768
769
# File 'lib/parse/model/classes/user.rb', line 766

def request_email_verification
  return false if email.nil?
  Parse::User.request_email_verification(email)
end

#request_password_resetBoolean

Request a password reset for this user

Returns:

  • (Boolean)

    true if it was successful requested. false otherwise.

Raises:

  • (Parse::Error::ServiceUnavailableError)

    if Parse Server returns a 500/503 (e.g. no emailAdapter / publicServerURL configured). Callers that branch on the Boolean should rescue this; if request_password_reset(email) is not exception-safe on a misconfigured server.

See Also:



755
756
757
758
# File 'lib/parse/model/classes/user.rb', line 755

def request_password_reset
  return false if email.nil?
  Parse::User.request_password_reset(email)
end

#sessionSession

Returns the session corresponding to the user's session token.

Returns:

  • (Session)

    the session corresponding to the user's session token.



929
930
931
932
933
934
935
936
937
938
939
940
# File 'lib/parse/model/classes/user.rb', line 929

def session
  if @session.blank? && @session_token.present?
    response = client.fetch_session(@session_token)
    # Trusted hydration: +response.result+ is the server-side
    # _Session row, which legitimately includes +sessionToken+,
    # +createdAt+, +updatedAt+, and other protected keys. Route
    # through {Parse::Object.build} which handles the trusted-init
    # signalling.
    @session ||= Parse::Object.build(response.result, Parse::Model::CLASS_SESSION)
  end
  @session
end

#session_client(base = self.client) ⇒ Parse::Client?

A non-master Client bound to this user's session token, for acting on the server as this user with full ACL / CLP / +protectedFields+ enforcement and no master-key fallback. It mirrors the connection settings of +base+ (the configured client by default) but carries no master key and binds #session_token, so even raw REST calls through it are authorized as the user with no per-call ceremony. The web-counterpart of Webhooks::Payload#user_client; the typical client-side entry point is right after a login:

client = Parse::User.login(username, password).session_client Parse::Query.new("Post", client: client).results # scoped to the user

Parameters:

  • base (Parse::Client) (defaults to: self.client)

    the client whose connection settings to mirror.

Returns:

  • (Parse::Client, nil)

    +nil+ when the user has no session token (e.g. fetched/saved under the master key rather than logged in).



957
958
959
960
# File 'lib/parse/model/classes/user.rb', line 957

def session_client(base = self.client)
  return nil if @session_token.nil? || @session_token.to_s.strip.empty?
  base.become(@session_token)
end

#sessionsArray<Parse::Session>

Get all active sessions for this user. Requires an authenticated session (see #logout_all! for the rationale).

Examples:

user.sessions.each do |session|
  puts "Session created: #{session.created_at}"
end

Returns:

Raises:



1381
1382
1383
1384
1385
1386
1387
# File 'lib/parse/model/classes/user.rb', line 1381

def sessions
  return [] unless id.present?
  require_self_session!("sessions")
  Parse.with_session(@session_token) do
    Parse::Session.for_user(self).all
  end
end

#signup!(passwd = nil) ⇒ Boolean

You may set a password for this user when you are creating them. Parse never returns a

Parameters:

  • passwd (defaults to: nil)

    The user's password to be used for signing up.

Returns:

  • (Boolean)

    True if signup it was successful. If it fails an exception is thrown.

Raises:



780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
# File 'lib/parse/model/classes/user.rb', line 780

def signup!(passwd = nil)
  self.password = passwd || password
  if username.blank?
    raise Parse::Error::UsernameMissingError, "Signup requires a username."
  end

  if password.blank?
    raise Parse::Error::PasswordMissingError, "Signup requires a password."
  end

   = attribute_updates
  # See {#signup_create} for the rationale on the safe-pattern check.
  if self.class.()
    .except!(:createdAt, :updatedAt, "createdAt", "updatedAt")
  else
    .except!(*Parse::Properties::BASE_FIELD_MAP.flatten)
  end
  self.class.strip_server_controlled_keys!()

  # first signup the user, then save any additional attributes
  response = client.create_user 

  if response.success?
    # Restrict what the server can plant into the in-memory user via
    # the signup response, matching the defense in {#signup_create}.
    # `POST /parse/users` legitimately returns objectId, createdAt,
    # updatedAt (extracted into @-vars directly below), sessionToken,
    # and emailVerified. Any other key in the response body --
    # `authData`, `_rperm`, `_wperm`, `roles`, etc. -- is dropped, so
    # a compromised or MITM'd Parse Server cannot use this code path
    # to plant credentials/permissions onto the user we just signed
    # up. The previous `apply_attributes! response.result` accepted
    # every key the server returned through the typed property
    # writers (`authData_set_attribute!` exists because we declare
    # `property :auth_data, :object`), which was a footgun the
    # save-as-signup path had already addressed.
    result = response.result
    @id = result[Parse::Model::OBJECT_ID] || @id
    @created_at = result["createdAt"] || @created_at
    @updated_at = result["updatedAt"] || result["createdAt"] || @updated_at
    set_attributes!(result.slice(*SIGNUP_RESPONSE_APPLY_KEYS))
    # Drop the plaintext password from memory now that the server
    # has it hashed and we no longer need it. Matches the Parse JS
    # SDK behavior of clearing the password attribute after a
    # successful save/signup. Uses direct ivar assignment so the
    # dirty tracker doesn't record this clear as a pending change
    # that would be re-sent on the next save.
    @password = nil
    # Mirror Parse::Object#save: a successful round-trip means the
    # locally-set credential fields are now in sync with the server
    # and must NOT be re-sent on the next save. Without this, a
    # subsequent user.save! re-transmits `password`, which Parse
    # Server treats as a password change under
    # revokeSessionOnPasswordReset and revokes the session just
    # minted by this signup.
    changes_applied!
    clear_partial_fetch_state!
    return true
  end

  case response.code
  when Parse::Response::ERROR_USERNAME_MISSING
    raise Parse::Error::UsernameMissingError, response
  when Parse::Response::ERROR_PASSWORD_MISSING
    raise Parse::Error::PasswordMissingError, response
  when Parse::Response::ERROR_USERNAME_TAKEN
    raise Parse::Error::UsernameTakenError, response
  when Parse::Response::ERROR_EMAIL_TAKEN
    raise Parse::Error::EmailTakenError, response
  when Parse::Response::ERROR_EMAIL_INVALID
    raise Parse::Error::InvalidEmailAddress, response
  end
  raise Parse::Client::ResponseError, response
end

#signup_on_saveBoolean

When true (default), saving a new Parse::User that has a password value routes through Parse Server's signup endpoint (POST /parse/users) with the X-Parse-Revocable-Session header set, so the signup response returns a session token that is applied to the in-memory user object via the standard sessionToken_set_attribute! hydration path. Without this flag, Parse::User.new(...).save! left session_token nil because the underlying create path did not request a revocable session.

Set to false to always create users without requesting a revocable session token - for example, when a master-key server-side script is provisioning user rows that will receive credentials later. New users created with no password always fall through to the standard create path regardless of this flag.

auth_data (federated identity / OAuth) signup is deliberately NOT triggered by this flag. POST /parse/users treats auth_data as a claim against an existing account, so allowing mass-assigned auth_data to trigger a revocable-session signup would let attacker-controlled params plant another user's session token onto the in-memory object. Use autologin_service or signup (the explicit class methods) for OAuth-driven signup; both bypass the mass-assignment filter because the caller is explicitly choosing the federated-identity flow.

Inherited through subclasses via ActiveSupport::Concern's class_attribute, so an application-specific subclass may override the default without affecting Parse::User itself.

Returns:

  • (Boolean)


233
# File 'lib/parse/model/classes/user.rb', line 233

class_attribute :signup_on_save, instance_writer: false

Removes third-party authentication data for this user

Parameters:

  • service_name (Symbol)

    The name of the third-party service (ex. :facebook)

Returns:

  • (Boolean)

    True/false if successful.

Raises:



637
638
639
640
641
# File 'lib/parse/model/classes/user.rb', line 637

def unlink_auth_data!(service_name)
  response = client.set_service_auth_data(id, service_name, nil)
  raise Parse::Client::ResponseError, response if response.error?
  self.class.with_authdata_trust { apply_attributes!(response.result) }
end

#upgrade_anonymous!(username:, password:, email: nil) ⇒ Boolean

Upgrade an anonymous user (one created via the +authData.anonymous+ provider) into a full username/password account. This is the SDK-side counterpart of the Parse JS SDK's +_linkWith('username', ...)+ flow — it sends a single +PUT /users/:id+ with the new credentials and an explicit +authData: { anonymous: nil }+ unlink in the same body, then narrowly applies the server's response to the in-memory user.

The +authData.anonymous+ unlink is essential: leaving the anonymous provider attached after assigning a username would let anyone else who somehow learned the (random) anonymous id silently log in as the freshly-named account, a documented Parse foot-gun.

Parameters:

  • username (String)

    the username to claim. Must be unique.

  • password (String)

    the password to set on the account.

  • email (String, nil) (defaults to: nil)

    optional email address. Must be unique if provided.

Returns:

  • (Boolean)

    true on success.

Raises:



672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
# File 'lib/parse/model/classes/user.rb', line 672

def upgrade_anonymous!(username:, password:, email: nil)
  require_self_session!(:upgrade_anonymous!)
  if @id.nil? || @id.to_s.empty?
    raise Parse::Error::AuthenticationError,
          "Parse::User#upgrade_anonymous! requires a saved user (no objectId)"
  end
  unless anonymous?
    raise Parse::Error::AuthenticationError,
          "Parse::User#upgrade_anonymous! is only valid for anonymous users " \
          "(authData.anonymous is not present on this instance)"
  end
  if username.nil? || username.to_s.empty?
    raise Parse::Error::UsernameMissingError, "upgrade_anonymous! requires a username."
  end
  if password.nil? || password.to_s.empty?
    raise Parse::Error::PasswordMissingError, "upgrade_anonymous! requires a password."
  end

  body = {
    username: username.to_s,
    password: password.to_s,
    # Explicitly unlink the anonymous provider in the same request
    # that claims the new credentials — otherwise the account
    # remains takeover-able via the anonymous id.
    authData: { anonymous: nil },
  }
  body[:email] = email.to_s if email.is_a?(String) && !email.empty?

  response = client.update_user(@id, body, session_token: @session_token)

  if response.success?
    result = response.result || {}
    @updated_at = result["updatedAt"] || @updated_at
    # Parse Server may rotate the session token on a credential
    # change; apply it narrowly if present without going through the
    # full property writer chain.
    if result["sessionToken"].is_a?(String) && !result["sessionToken"].empty?
      @session_token = result["sessionToken"]
    end
    @auth_data.delete("anonymous") if @auth_data.is_a?(Hash)
    @username = username.to_s
    @email = email.to_s if email.is_a?(String) && !email.empty?
    @password = nil
    changes_applied!
    clear_partial_fetch_state!
    return true
  end

  case response.code
  when Parse::Response::ERROR_USERNAME_MISSING
    raise Parse::Error::UsernameMissingError, response
  when Parse::Response::ERROR_PASSWORD_MISSING
    raise Parse::Error::PasswordMissingError, response
  when Parse::Response::ERROR_USERNAME_TAKEN
    raise Parse::Error::UsernameTakenError, response
  when Parse::Response::ERROR_EMAIL_TAKEN
    raise Parse::Error::EmailTakenError, response
  when Parse::Response::ERROR_EMAIL_INVALID
    raise Parse::Error::InvalidEmailAddress, response
  end
  raise Parse::Client::ResponseError, response
end

#verify_password(password) ⇒ Boolean

Verify this user's password without minting a session token.

Delegates to the +GET /parse/verifyPassword+ endpoint (Parse Server 7.1.0+) using this user's +username+ and the supplied +password+. The check is purely credential validation — no session is created on success, and the user's existing sessions are unaffected.

Use this as a step-up authentication gate: before allowing a sensitive action (e.g. changing an email address or deleting an account), call +verify_password+ to confirm the caller still knows the password.

Examples:

# Step-up check before a destructive action
if user.verify_password(params[:current_password])
  user.destroy
end

Parameters:

  • password (String)

    the password to verify.

Returns:

  • (Boolean)

    +true+ if the credentials are valid.

  • (Boolean)

Raises:

  • (Parse::Error::EmailNotVerifiedError)

    when the account exists but +preventLoginWithUnverifiedEmail+ is enabled and the email has not been verified (Parse Server error code 205). The caller may want to prompt the user to check their inbox rather than treating this as a wrong- password failure.

  • (Parse::Error::AuthenticationError)

    when the username does not exist or the password is wrong (code 101, +OBJECT_NOT_FOUND+).



1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
# File 'lib/parse/model/classes/user.rb', line 1425

def verify_password(password)
  response = client.verify_password(username.to_s, password.to_s)
  return true if response.success?

  case response.code
  when Parse::Response::ERROR_EMAIL_NOT_FOUND
    raise Parse::Error::EmailNotVerifiedError,
          "verify_password failed: email address is not verified (code=205)"
  else
    raise Parse::Error::AuthenticationError,
          "verify_password failed: " \
          "#{response.error || "HTTP #{response.http_status}"} (code=#{response.code.inspect})"
  end
end

#with_session { ... } ⇒ Object

Block-scoped sugar around Parse.with_session: runs the block with this user's session_token as the ambient session token for the current fiber. Every Parse call inside the block that doesn't explicitly pass session_token: or use_master_key: true will be sent as this user.

Examples:

user = Parse::User.login!("alice", "pw")
user.with_session do
  Post.all                # scoped to alice
  post.save               # scoped to alice
end

Yields:

  • runs the block with the user's session in ambient scope.

Returns:

  • (Object)

    the block's return value.

Raises:



1282
1283
1284
1285
1286
1287
1288
1289
1290
# File 'lib/parse/model/classes/user.rb', line 1282

def with_session(&block)
  raise ArgumentError, "Parse::User#with_session requires a block" unless block_given?
  unless @session_token.is_a?(String) && !@session_token.empty?
    raise Parse::Error::AuthenticationError,
          "Parse::User#with_session requires an authenticated session — " \
          "obtain the instance via login/signup or call `user.session_token = '...'` first"
  end
  Parse.with_session(@session_token, &block)
end