Class: Parse::User
- Inherits:
-
Object
- Object
- Object
- 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
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.
-
#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.
Class Method Summary collapse
-
.anonymous_signup ⇒ User
Create and log in a new anonymous user via the
authData.anonymousprovider. -
.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.
-
.request_password_reset(email) ⇒ Boolean
Request a password reset for a registered email.
-
.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 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.anonymousprovider 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_password_reset ⇒ Boolean
Request a password reset for this user.
-
#session ⇒ Session
The session corresponding to the user's session token.
-
#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.anonymousprovider) into a full username/password account. -
#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!
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.
226 |
# File 'lib/parse/model/classes/user.rb', line 226 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.
191 |
# File 'lib/parse/model/classes/user.rb', line 191 property :auth_data, :object |
#email ⇒ String
Emails are optional in Parse, but if set, they must be unique.
196 |
# File 'lib/parse/model/classes/user.rb', line 196 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).
218 |
# File 'lib/parse/model/classes/user.rb', line 218 property :email_verified, :boolean |
#session_token ⇒ String
Returns The session token if this user is logged in.
184 185 186 |
# File 'lib/parse/model/classes/user.rb', line 184 def session_token @session_token end |
#username ⇒ String
All Parse users have a username and must be globally unique.
209 |
# File 'lib/parse/model/classes/user.rb', line 209 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.
796 797 798 |
# File 'lib/parse/model/classes/user.rb', line 796 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.
771 772 773 774 775 776 777 778 779 780 781 |
# File 'lib/parse/model/classes/user.rb', line 771 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.
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 |
# File 'lib/parse/model/classes/user.rb', line 696 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.
817 818 819 820 821 822 823 |
# File 'lib/parse/model/classes/user.rb', line 817 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.
838 839 840 841 842 843 844 845 846 847 848 |
# File 'lib/parse/model/classes/user.rb', line 838 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 raise Parse::Error::AuthenticationError, "Parse::User.login! failed for #{username.inspect}: " \ "#{response.error || "HTTP #{response.http_status}"} (code=#{response.code.inspect})" end end |
.request_password_reset(email) ⇒ Boolean
Request a password reset for a registered email.
860 861 862 863 864 865 |
# File 'lib/parse/model/classes/user.rb', line 860 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 |
.session(token, opts = {}) ⇒ User
Same as session! but returns nil if a user was not found or sesion token was invalid.
870 871 872 873 874 |
# File 'lib/parse/model/classes/user.rb', line 870 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.
884 885 886 887 888 889 890 891 892 893 894 895 896 897 |
# File 'lib/parse/model/classes/user.rb', line 884 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.
806 807 808 809 810 |
# File 'lib/parse/model/classes/user.rb', line 806 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 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.
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 |
# File 'lib/parse/model/classes/user.rb', line 1140 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.
1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 |
# File 'lib/parse/model/classes/user.rb', line 1083 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).
997 998 999 1000 1001 1002 1003 |
# File 'lib/parse/model/classes/user.rb', line 997 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).
315 316 317 |
# File 'lib/parse/model/classes/user.rb', line 315 def anonymous? !anonymous_id.nil? end |
#anonymous_id ⇒ String
Returns the anonymous identifier only if this user is anonymous.
322 323 324 |
# File 'lib/parse/model/classes/user.rb', line 322 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.
929 930 931 932 933 934 935 |
# File 'lib/parse/model/classes/user.rb', line 929 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.
330 331 332 333 334 |
# File 'lib/parse/model/classes/user.rb', line 330 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.
447 448 449 |
# File 'lib/parse/model/classes/user.rb', line 447 def logged_in? self.session_token.present? end |
#login!(passwd = nil) ⇒ Boolean
Login and get a session token for this user.
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 |
# File 'lib/parse/model/classes/user.rb', line 574 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.
601 602 603 604 605 606 607 608 |
# File 'lib/parse/model/classes/user.rb', line 601 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.
960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 |
# File 'lib/parse/model/classes/user.rb', line 960 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).
1027 1028 1029 |
# File 'lib/parse/model/classes/user.rb', line 1027 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!)
204 |
# File 'lib/parse/model/classes/user.rb', line 204 property :password |
#request_password_reset ⇒ Boolean
Request a password reset for this user
454 455 456 457 |
# File 'lib/parse/model/classes/user.rb', line 454 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.
617 618 619 620 621 622 623 624 625 626 627 628 |
# File 'lib/parse/model/classes/user.rb', line 617 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 |
#sessions ⇒ Array<Parse::Session>
Get all active sessions for this user. Requires an authenticated session (see #logout_all! for the rationale).
1013 1014 1015 1016 1017 1018 1019 |
# File 'lib/parse/model/classes/user.rb', line 1013 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
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 |
# File 'lib/parse/model/classes/user.rb', line 468 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.
180 |
# File 'lib/parse/model/classes/user.rb', line 180 class_attribute :signup_on_save, instance_writer: false |
#unlink_auth_data!(service_name) ⇒ Boolean
Removes third-party authentication data for this user
340 341 342 343 344 |
# File 'lib/parse/model/classes/user.rb', line 340 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.
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 |
# File 'lib/parse/model/classes/user.rb', line 375 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 |
#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.
914 915 916 917 918 919 920 921 922 |
# File 'lib/parse/model/classes/user.rb', line 914 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 |