Class: Parse::User
- 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
-
#active_sessions ⇒ Array<Parse::Session>
A has_many relationship to all Session instances for this user.
-
#auth_data ⇒ Hash
The auth data for this Parse::User.
-
#email ⇒ String
Emails are optional in Parse, but if set, they must be unique.
-
#email_verified ⇒ Boolean
Whether this user's email address has been verified.
-
#installations ⇒ Array<Parse::Installation>
A
has_manyquery-form association resolving to all Installation records whoseuserpointer is this user. -
#session_token ⇒ String
readonly
The session token if this user is logged in.
-
#username ⇒ String
All Parse users have a username and must be globally unique.
Attributes inherited from Object
#acl, #created_at, #id, #updated_at
Attributes inherited from Pointer
Class Method Summary collapse
-
.anonymous_signup ⇒ User
Create and log in a new anonymous user via the +authData.anonymous+ provider.
-
.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.
-
.create(body, **opts) ⇒ User
Creates a new Parse::User given a hash that maps to the fields defined in your Parse::User collection.
-
.login(username, password) ⇒ User
Login and return a Parse::User with this username/password combination.
-
.login!(username, password) ⇒ User
Login and return a Parse::User with this username/password combination, raising on failure instead of returning nil.
-
.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.
-
.protect_fields(pattern, fields) ⇒ Object
Override Object.protect_fields on
_Userso that ad-hoc uses (i.e. not through User.master_only_fields / User.self_visible_fields) emit a one-time advisory pointing at the higher-level helpers and theprotectedFieldsOwnerExemptflag. -
.request_email_verification(email) ⇒ Boolean
Request that Parse Server (re)send the email-address verification email for a registered, not-yet-verified user.
-
.request_password_reset(email) ⇒ Boolean
Request a password reset for a registered email.
-
.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.
-
.session(token, opts = {}) ⇒ User
Same as
session!but returns nil if a user was not found or sesion token was invalid. -
.session!(token, opts = {}) ⇒ User
Return a Parse::User for this active session token.
-
.signup(username, password, email = nil, body: {}) ⇒ Object
This method will signup a new user using the parameters below.
-
.signup_body_self_only_acl_safe?(body) ⇒ Boolean
private
True when the signup-body
objectIdandACLtogether describe the safe self-only ownership pattern that Object.acl_policy produces underowner: :self: the body has a client-assignedobjectIdmatching the Parse-id format, and the ACL has exactly one entry granting read+write to that same objectId.
Instance Method Summary collapse
-
#acl_roles(max_depth: 10, master: false, as: nil) ⇒ Set<String>
Return the transitive upward closure of role names this user inherits permissions from.
-
#active_session_count ⇒ Integer
Get the count of active (non-expired) sessions for this user.
-
#anonymous? ⇒ 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).
-
#anonymous_id ⇒ String
Returns the anonymous identifier only if this user is anonymous.
-
#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.
-
#link_auth_data!(service_name, **data) ⇒ Object
Adds the third-party authentication data to for a given service.
-
#logged_in? ⇒ Boolean
True if this user has a session token.
-
#login!(passwd = nil) ⇒ Boolean
Login and get a session token for this user.
-
#logout ⇒ Boolean
Invalid the current session token for this logged in user.
-
#logout_all!(keep_current: false) ⇒ Integer
Logout from all sessions, effectively signing out on all devices.
-
#multi_session? ⇒ Boolean
Check if this user has multiple active sessions (logged in on multiple devices).
-
#password=(value) ⇒ String
You may set a password for this user when you are creating them.
-
#request_email_verification ⇒ Boolean
Request that Parse Server (re)send this user's email-address verification email.
-
#request_password_reset ⇒ Boolean
Request a password reset for this user.
-
#session ⇒ Session
The session corresponding to the user's session token.
-
#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.
-
#sessions ⇒ Array<Parse::Session>
Get all active sessions for this user.
-
#signup!(passwd = nil) ⇒ Boolean
You may set a password for this user when you are creating them.
-
#signup_on_save ⇒ Boolean
When true (default), saving a new User that has a
passwordvalue routes through Parse Server's signup endpoint (POST /parse/users) with theX-Parse-Revocable-Sessionheader set, so the signup response returns a session token that is applied to the in-memory user object via the standardsessionToken_set_attribute!hydration path. -
#unlink_auth_data!(service_name) ⇒ Boolean
Removes third-party authentication data for this user.
-
#upgrade_anonymous!(username:, password:, email: nil) ⇒ Boolean
Upgrade an anonymous user (one created via the +authData.anonymous+ provider) into a full username/password account.
-
#verify_password(password) ⇒ Boolean
Verify this user's password without minting a session token.
-
#with_session { ... } ⇒ Object
Block-scoped sugar around with_session: runs the block with this user's
session_tokenas the ambient session token for the current fiber.
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
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
Methods included from Associations::HasMany
has_many, #relation_changes?, #relation_updates, #relations
Methods included from Associations::BelongsTo
Methods included from Associations::HasOne
Methods included from Core::ParseReference
format, generate_object_id, parse
Methods included from Core::FieldGuards
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
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_sessions ⇒ Array<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.
279 |
# File 'lib/parse/model/classes/user.rb', line 279 has_many :active_sessions, as: :session |
#auth_data ⇒ Hash
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.
244 |
# File 'lib/parse/model/classes/user.rb', line 244 property :auth_data, :object |
#email ⇒ String
Emails are optional in Parse, but if set, they must be unique.
249 |
# File 'lib/parse/model/classes/user.rb', line 249 property :email |
#email_verified ⇒ Boolean
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+).
271 |
# File 'lib/parse/model/classes/user.rb', line 271 property :email_verified, :boolean |
#installations ⇒ Array<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_token ⇒ String
Returns 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 |
#username ⇒ String
All Parse users have a username and must be globally unique.
262 |
# File 'lib/parse/model/classes/user.rb', line 262 property :username |
Class Method Details
.anonymous_signup ⇒ User
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.
1128 1129 1130 |
# File 'lib/parse/model/classes/user.rb', line 1128 def self.anonymous_signup 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.
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.
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.
1149 1150 1151 1152 1153 1154 1155 |
# File 'lib/parse/model/classes/user.rb', line 1149 def self.login(username, password) response = client.login(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.
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.login(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.
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.
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.
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 viavia:, default:self) that is set to the row's own pointer by abeforeSave('_User')Cloud Code trigger.
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.
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.
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.
1138 1139 1140 1141 1142 |
# File 'lib/parse/model/classes/user.rb', line 1138 def self.signup(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.
1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 |
# File 'lib/parse/model/classes/user.rb', line 1549 def self.signup_body_self_only_acl_safe?(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.
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_count ⇒ Integer
Get the count of active (non-expired) sessions for this user. Requires an authenticated session (see #logout_all! for the rationale).
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).
612 613 614 |
# File 'lib/parse/model/classes/user.rb', line 612 def anonymous? !anonymous_id.nil? end |
#anonymous_id ⇒ String
Returns the anonymous identifier only if this user is anonymous.
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.
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 |
#link_auth_data!(service_name, **data) ⇒ Object
Adds the third-party authentication data to for a given service.
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.
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.
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.login(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 |
#logout ⇒ Boolean
Invalid the current session token for this logged in user.
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.
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).
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!)
257 |
# File 'lib/parse/model/classes/user.rb', line 257 property :password |
#request_email_verification ⇒ Boolean
Request that Parse Server (re)send this user's email-address verification
email. The server must have an email adapter and verifyUserEmails enabled.
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_reset ⇒ Boolean
Request a password reset for this user
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 |
#session ⇒ Session
Returns 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
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 |
#sessions ⇒ Array<Parse::Session>
Get all active sessions for this user. Requires an authenticated session (see #logout_all! for the rationale).
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
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 signup_attrs = attribute_updates # See {#signup_create} for the rationale on the safe-pattern check. if self.class.signup_body_self_only_acl_safe?(signup_attrs) signup_attrs.except!(:createdAt, :updatedAt, "createdAt", "updatedAt") else signup_attrs.except!(*Parse::Properties::BASE_FIELD_MAP.flatten) end self.class.strip_server_controlled_keys!(signup_attrs) # first signup the user, then save any additional attributes response = client.create_user signup_attrs 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_save ⇒ Boolean
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.
233 |
# File 'lib/parse/model/classes/user.rb', line 233 class_attribute :signup_on_save, instance_writer: false |
#unlink_auth_data!(service_name) ⇒ Boolean
Removes third-party authentication data for this user
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.
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.
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.
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 |