/** * Copyright (c) 2015-present, Parse, LLC. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @flow */ import AnonymousUtils from './AnonymousUtils'; import CoreManager from './CoreManager'; import isRevocableSession from './isRevocableSession'; import ParseError from './ParseError'; import ParseObject from './ParseObject'; import ParseSession from './ParseSession'; import Storage from './Storage'; /*:: import type { AttributeMap } from './ObjectStateMutations';*/ /*:: import type { RequestOptions, FullOptions } from './RESTController';*/ /*:: export type AuthData = ?{ [key: string]: mixed };*/ const CURRENT_USER_KEY = 'currentUser'; let canUseCurrentUser = !CoreManager.get('IS_NODE'); let currentUserCacheMatchesDisk = false; let currentUserCache = null; const authProviders = {}; /** *
A Parse.User object is a local representation of a user persisted to the * Parse cloud. This class is a subclass of a Parse.Object, and retains the * same functionality of a Parse.Object, but also extends it with various * user specific methods, like authentication, signing up, and validation of * uniqueness.
* @alias Parse.User * @extends Parse.Object */ class ParseUser extends ParseObject { /** * @param {Object} attributes The initial set of data to store in the user. */ constructor(attributes /*: ?AttributeMap*/ ) { super('_User'); if (attributes && typeof attributes === 'object') { if (!this.set(attributes || {})) { throw new Error('Can\'t create an invalid Parse User'); } } } /** * Request a revocable session token to replace the older style of token. * @param {Object} options * @return {Promise} A promise that is resolved when the replacement * token has been fetched. */ _upgradeToRevocableSession(options /*: RequestOptions*/ ) /*: Promisecurrent
would return this user.
* @return {Boolean}
*/
isCurrent()
/*: boolean*/
{
const current = ParseUser.current();
return !!current && current.id === this.id;
}
/**
* Returns get("username").
* @return {String}
*/
getUsername()
/*: ?string*/
{
const username = this.get('username');
if (username == null || typeof username === 'string') {
return username;
}
return '';
}
/**
* Calls set("username", username, options) and returns the result.
* @param {String} username
* @param {Object} options
* @return {Boolean}
*/
setUsername(username
/*: string*/
) {
// Strip anonymity, even we do not support anonymous user in js SDK, we may
// encounter anonymous user created by android/iOS in cloud code.
const authData = this.get('authData');
if (authData && typeof authData === 'object' && authData.hasOwnProperty('anonymous')) {
// We need to set anonymous to null instead of deleting it in order to remove it from Parse.
authData.anonymous = null;
}
this.set('username', username);
}
/**
* Calls set("password", password, options) and returns the result.
* @param {String} password
* @param {Object} options
* @return {Boolean}
*/
setPassword(password
/*: string*/
) {
this.set('password', password);
}
/**
* Returns get("email").
* @return {String}
*/
getEmail()
/*: ?string*/
{
const email = this.get('email');
if (email == null || typeof email === 'string') {
return email;
}
return '';
}
/**
* Calls set("email", email) and returns the result.
* @param {String} email
* @return {Boolean}
*/
setEmail(email
/*: string*/
) {
return this.set('email', email);
}
/**
* Returns the session token for this user, if the user has been logged in,
* or if it is the result of a query with the master key. Otherwise, returns
* undefined.
* @return {String} the session token, or undefined
*/
getSessionToken()
/*: ?string*/
{
const token = this.get('sessionToken');
if (token == null || typeof token === 'string') {
return token;
}
return '';
}
/**
* Checks whether this user is the current user and has been authenticated.
* @return (Boolean) whether this user is the current user and is logged in.
*/
authenticated()
/*: boolean*/
{
const current = ParseUser.current();
return !!this.get('sessionToken') && !!current && current.id === this.id;
}
/**
* Signs up a new user. You should call this instead of save for
* new Parse.Users. This will create a new Parse.User on the server, and
* also persist the session on disk so that you can access the user using
* current
.
*
* A username and password must be set before calling signUp.
* *Calls options.success or options.error on completion.
* * @param {Object} attrs Extra fields to set on the new user, or null. * @param {Object} options * @return {Promise} A promise that is fulfilled when the signup * finishes. */ signUp(attrs /*: AttributeMap*/ , options /*:: ?: FullOptions*/ ) /*: Promisecurrent
.
*
* A username and password must be set before calling logIn.
* *Calls options.success or options.error on completion.
* * @param {Object} options * @return {Promise} A promise that is fulfilled with the user when * the login is complete. */ logIn(options /*:: ?: FullOptions*/ ) /*: PromiseCalls options.success or options.error on completion.
* * @param {String} username The username (or email) to sign up with. * @param {String} password The password to sign up with. * @param {Object} attrs Extra fields to set on the new user. * @param {Object} options * @static * @return {Promise} A promise that is fulfilled with the user when * the signup completes. */ static signUp(username /*: string*/ , password /*: string*/ , attrs /*: AttributeMap*/ , options /*:: ?: FullOptions*/ ) { attrs = attrs || {}; attrs.username = username; attrs.password = password; const user = new this(attrs); return user.signUp({}, options); } /** * Logs in a user with a username (or email) and password. On success, this * saves the session to disk, so you can retrieve the currently logged in * user usingcurrent
.
*
* Calls options.success or options.error on completion.
* * @param {String} username The username (or email) to log in with. * @param {String} password The password to log in with. * @param {Object} options * @static * @return {Promise} A promise that is fulfilled with the user when * the login completes. */ static logIn(username /*: string*/ , password /*: string*/ , options /*:: ?: FullOptions*/ ) { if (typeof username !== 'string') { return Promise.reject(new ParseError(ParseError.OTHER_CAUSE, 'Username must be a string.')); } else if (typeof password !== 'string') { return Promise.reject(new ParseError(ParseError.OTHER_CAUSE, 'Password must be a string.')); } const user = new this(); user._finishFetch({ username: username, password: password }); return user.logIn(options); } /** * Logs in a user with a session token. On success, this saves the session * to disk, so you can retrieve the currently logged in user using *current
.
*
* Calls options.success or options.error on completion.
* * @param {String} sessionToken The sessionToken to log in with. * @param {Object} options * @static * @return {Promise} A promise that is fulfilled with the user when * the login completes. */ static become(sessionToken /*: string*/ , options /*:: ?: RequestOptions*/ ) { if (!canUseCurrentUser) { throw new Error('It is not memory-safe to become a user in a server environment'); } options = options || {}; const becomeOptions /*: RequestOptions*/ = { sessionToken: sessionToken }; if (options.hasOwnProperty('useMasterKey')) { becomeOptions.useMasterKey = options.useMasterKey; } const controller = CoreManager.getUserController(); return controller.become(becomeOptions); } /** * Retrieves a user with a session token. * * @param {String} sessionToken The sessionToken to get user with. * @param {Object} options * @static * @return {Promise} A promise that is fulfilled with the user is fetched. */ static me(sessionToken /*: string*/ , options /*:: ?: RequestOptions*/ = {}) { const controller = CoreManager.getUserController(); const meOptions /*: RequestOptions*/ = { sessionToken: sessionToken }; if (options.useMasterKey) { meOptions.useMasterKey = options.useMasterKey; } return controller.me(meOptions); } /** * Logs in a user with a session token. On success, this saves the session * to disk, so you can retrieve the currently logged in user using *current
. If there is no session token the user will not logged in.
*
* @param {Object} userJSON The JSON map of the User's data
* @static
* @return {Promise} A promise that is fulfilled with the user when
* the login completes.
*/
static hydrate(userJSON
/*: AttributeMap*/
) {
const controller = CoreManager.getUserController();
return controller.hydrate(userJSON);
}
static logInWith(provider
/*: any*/
, options
/*: { authData?: AuthData }*/
, saveOpts
/*:: ?: FullOptions*/
) {
return ParseUser._logInWith(provider, options, saveOpts);
}
/**
* Logs out the currently logged in user session. This will remove the
* session from disk, log out of linked services, and future calls to
* current
will return null
.
*
* @param {Object} options
* @static
* @return {Promise} A promise that is resolved when the session is
* destroyed on the server.
*/
static logOut(options
/*: RequestOptions*/
= {}) {
const controller = CoreManager.getUserController();
return controller.logOut(options);
}
/**
* Requests a password reset email to be sent to the specified email address
* associated with the user account. This email allows the user to securely
* reset their password on the Parse site.
*
* Calls options.success or options.error on completion.
* * @param {String} email The email address associated with the user that * forgot their password. * @param {Object} options * @static * @returns {Promise} */ static requestPasswordReset(email /*: string*/ , options /*:: ?: RequestOptions*/ ) { options = options || {}; const requestOptions = {}; if (options.hasOwnProperty('useMasterKey')) { requestOptions.useMasterKey = options.useMasterKey; } const controller = CoreManager.getUserController(); return controller.requestPasswordReset(email, requestOptions); } /** * Allow someone to define a custom User class without className * being rewritten to _User. The default behavior is to rewrite * User to _User for legacy reasons. This allows developers to * override that behavior. * * @param {Boolean} isAllowed Whether or not to allow custom User class * @static */ static allowCustomUserClass(isAllowed /*: boolean*/ ) { CoreManager.set('PERFORM_USER_REWRITE', !isAllowed); } /** * Allows a legacy application to start using revocable sessions. If the * current session token is not revocable, a request will be made for a new, * revocable session. * It is not necessary to call this method from cloud code unless you are * handling user signup or login from the server side. In a cloud code call, * this function will not attempt to upgrade the current token. * @param {Object} options * @static * @return {Promise} A promise that is resolved when the process has * completed. If a replacement session token is requested, the promise * will be resolved after a new token has been fetched. */ static enableRevocableSession(options /*:: ?: RequestOptions*/ ) { options = options || {}; CoreManager.set('FORCE_REVOCABLE_SESSION', true); if (canUseCurrentUser) { const current = ParseUser.current(); if (current) { return current._upgradeToRevocableSession(options); } } return Promise.resolve(); } /** * Enables the use of become or the current user in a server * environment. These features are disabled by default, since they depend on * global objects that are not memory-safe for most servers. * @static */ static enableUnsafeCurrentUser() { canUseCurrentUser = true; } /** * Disables the use of become or the current user in any environment. * These features are disabled on servers by default, since they depend on * global objects that are not memory-safe for most servers. * @static */ static disableUnsafeCurrentUser() { canUseCurrentUser = false; } static _registerAuthenticationProvider(provider /*: any*/ ) { authProviders[provider.getAuthType()] = provider; // Synchronize the current user with the auth provider. ParseUser.currentAsync().then(current => { if (current) { current._synchronizeAuthData(provider.getAuthType()); } }); } static _logInWith(provider /*: any*/ , options /*: { authData?: AuthData }*/ , saveOpts /*:: ?: FullOptions*/ ) { const user = new ParseUser(); return user._linkWith(provider, options, saveOpts); } static _clearCache() { currentUserCache = null; currentUserCacheMatchesDisk = false; } static _setCurrentUserCache(user /*: ParseUser*/ ) { currentUserCache = user; } } ParseObject.registerSubclass('_User', ParseUser); const DefaultController = { updateUserOnDisk(user) { const path = Storage.generatePath(CURRENT_USER_KEY); const json = user.toJSON(); json.className = '_User'; return Storage.setItemAsync(path, JSON.stringify(json)).then(() => { return user; }); }, removeUserFromDisk() { const path = Storage.generatePath(CURRENT_USER_KEY); currentUserCacheMatchesDisk = true; currentUserCache = null; return Storage.removeItemAsync(path); }, setCurrentUser(user) { const currentUser = this.currentUser(); let promise = Promise.resolve(); if (currentUser && !user.equals(currentUser) && AnonymousUtils.isLinked(currentUser)) { promise = currentUser.destroy({ sessionToken: currentUser.getSessionToken() }); } currentUserCache = user; user._cleanupAuthData(); user._synchronizeAllAuthData(); return promise.then(() => DefaultController.updateUserOnDisk(user)); }, currentUser() /*: ?ParseUser*/ { if (currentUserCache) { return currentUserCache; } if (currentUserCacheMatchesDisk) { return null; } if (Storage.async()) { throw new Error('Cannot call currentUser() when using a platform with an async ' + 'storage system. Call currentUserAsync() instead.'); } const path = Storage.generatePath(CURRENT_USER_KEY); let userData = Storage.getItem(path); currentUserCacheMatchesDisk = true; if (!userData) { currentUserCache = null; return null; } userData = JSON.parse(userData); if (!userData.className) { userData.className = '_User'; } if (userData._id) { if (userData.objectId !== userData._id) { userData.objectId = userData._id; } delete userData._id; } if (userData._sessionToken) { userData.sessionToken = userData._sessionToken; delete userData._sessionToken; } const current = ParseObject.fromJSON(userData); currentUserCache = current; current._synchronizeAllAuthData(); return current; }, currentUserAsync() /*: Promise*/ { if (currentUserCache) { return Promise.resolve(currentUserCache); } if (currentUserCacheMatchesDisk) { return Promise.resolve(null); } const path = Storage.generatePath(CURRENT_USER_KEY); return Storage.getItemAsync(path).then(userData => { currentUserCacheMatchesDisk = true; if (!userData) { currentUserCache = null; return Promise.resolve(null); } userData = JSON.parse(userData); if (!userData.className) { userData.className = '_User'; } if (userData._id) { if (userData.objectId !== userData._id) { userData.objectId = userData._id; } delete userData._id; } if (userData._sessionToken) { userData.sessionToken = userData._sessionToken; delete userData._sessionToken; } const current = ParseObject.fromJSON(userData); currentUserCache = current; current._synchronizeAllAuthData(); return Promise.resolve(current); }); }, signUp(user /*: ParseUser*/ , attrs /*: AttributeMap*/ , options /*: RequestOptions*/ ) /*: Promise