UNPKG

mongodb-stitch

Version:

[![Join the chat at https://gitter.im/mongodb/stitch](https://badges.gitter.im/mongodb/stitch.svg)](https://gitter.im/mongodb/stitch?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

637 lines (524 loc) 21.9 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.StitchClient = exports.StitchClientFactory = undefined; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* global window, fetch */ /* eslint no-labels: ['error', { 'allowLoop': true }] */ exports.newStitchClient = newStitchClient; require('fetch-everywhere'); var _auth = require('./auth'); var _providers = require('./auth/providers'); var _common = require('./auth/common'); var _services = require('./services'); var _services2 = _interopRequireDefault(_services); var _common2 = require('./common'); var common = _interopRequireWildcard(_common2); var _mongodbExtjson = require('mongodb-extjson'); var _mongodbExtjson2 = _interopRequireDefault(_mongodbExtjson); var _queryString = require('query-string'); var _queryString2 = _interopRequireDefault(_queryString); var _errors = require('./errors'); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var v1 = 1; var v2 = 2; var v3 = 3; var API_TYPE_PUBLIC = 'public'; var API_TYPE_PRIVATE = 'private'; var API_TYPE_CLIENT = 'client'; var API_TYPE_APP = 'app'; /** * StitchClientFactory is a singleton factory class which can be used to * asynchronously create instances of {@link StitchClient}. StitchClientFactory * is not meant to be instantiated. Use the static `create()` method to build * a new StitchClient. */ var StitchClientFactory = exports.StitchClientFactory = function () { /** * @hideconstructor */ function StitchClientFactory() { _classCallCheck(this, StitchClientFactory); throw new _errors.StitchError('StitchClient can only be made from the StitchClientFactory.create function'); } /** * Creates a new {@link StitchClient}. * * @param {String} clientAppID the app ID of the Stitch application, which can be found in * the "Clients" page of the Stitch admin console. * @param {Object} [options = {}] additional options for creating the {@link StitchClient}. */ _createClass(StitchClientFactory, null, [{ key: 'create', value: function create(clientAppID) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return newStitchClient(StitchClient.prototype, clientAppID, options); } }]); return StitchClientFactory; }(); function newStitchClient(prototype, clientAppID) { var _v, _v2, _v3, _stitchClient$rootURL; var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var stitchClient = Object.create(prototype); var baseUrl = common.DEFAULT_STITCH_SERVER_URL; if (options.baseUrl) { baseUrl = options.baseUrl; } stitchClient.clientAppID = clientAppID; stitchClient.authUrl = clientAppID ? baseUrl + '/api/client/v2.0/app/' + clientAppID + '/auth' : baseUrl + '/api/admin/v3.0/auth'; stitchClient.rootURLsByAPIVersion = (_stitchClient$rootURL = {}, _defineProperty(_stitchClient$rootURL, v1, (_v = {}, _defineProperty(_v, API_TYPE_PUBLIC, baseUrl + '/api/public/v1.0'), _defineProperty(_v, API_TYPE_CLIENT, baseUrl + '/api/client/v1.0'), _defineProperty(_v, API_TYPE_PRIVATE, baseUrl + '/api/private/v1.0'), _defineProperty(_v, API_TYPE_APP, clientAppID ? baseUrl + '/api/client/v1.0/app/' + clientAppID : baseUrl + '/api/public/v1.0'), _v)), _defineProperty(_stitchClient$rootURL, v2, (_v2 = {}, _defineProperty(_v2, API_TYPE_PUBLIC, baseUrl + '/api/public/v2.0'), _defineProperty(_v2, API_TYPE_CLIENT, baseUrl + '/api/client/v2.0'), _defineProperty(_v2, API_TYPE_PRIVATE, baseUrl + '/api/private/v2.0'), _defineProperty(_v2, API_TYPE_APP, clientAppID ? baseUrl + '/api/client/v2.0/app/' + clientAppID : baseUrl + '/api/public/v2.0'), _v2)), _defineProperty(_stitchClient$rootURL, v3, (_v3 = {}, _defineProperty(_v3, API_TYPE_PUBLIC, baseUrl + '/api/public/v3.0'), _defineProperty(_v3, API_TYPE_CLIENT, baseUrl + '/api/client/v3.0'), _defineProperty(_v3, API_TYPE_APP, clientAppID ? baseUrl + '/api/client/v3.0/app/' + clientAppID : baseUrl + '/api/admin/v3.0'), _v3)), _stitchClient$rootURL); var authOptions = { codec: _common.APP_CLIENT_CODEC, storage: options.storage }; if (options.storageType) { authOptions.storageType = options.storageType; } if (options.platform) { authOptions.platform = options.platform; } if (options.authCodec) { authOptions.codec = options.authCodec; } if (options.requestOrigin) { authOptions.requestOrigin = options.requestOrigin; } var authPromise = _auth.AuthFactory.create(stitchClient, stitchClient.authUrl, authOptions); return authPromise.then(function (auth) { stitchClient.auth = auth; return Promise.all([stitchClient.auth.handleRedirect(), stitchClient.auth.handleCookie()]); }).then(function () { return stitchClient; }); } /** * StitchClient is the fundamental way of communicating with MongoDB Stitch in your * application. Use StitchClient to authenticate users and to access Stitch services. * StitchClient is not meant to be instantiated directly. Use a * {@link StitchClientFactory} to create one. */ var StitchClient = exports.StitchClient = function () { /** * @hideconstructor */ function StitchClient() { _classCallCheck(this, StitchClient); var classname = this.constructor.name; throw new _errors.StitchError(classname + ' can only be made from the ' + classname + 'Factory.create function'); } _createClass(StitchClient, [{ key: 'login', /** * Login to Stitch instance, optionally providing a username and password. In * the event that these are omitted, anonymous authentication is used. * * @param {String} [email] the email address used for login * @param {String} [password] the password for the provided email address * @param {Object} [options = {}] additional authentication options * @returns {Promise} which resolve to a String value: the authenticated user ID. */ value: function login(email, password) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; if (email === undefined || password === undefined) { return this.authenticate(_providers.PROVIDER_TYPE_ANON, options); } return this.authenticate('userpass', Object.assign({ username: email, password: password }, options)); } /** * Send a request to the server indicating the provided email would like * to sign up for an account. This will trigger a confirmation email containing * a token which must be used with the `emailConfirm` method of the `userpass` * auth provider in order to complete registration. The user will not be able * to log in until that flow has been completed. * * @param {String} email the email used to sign up for the app * @param {String} password the password used to sign up for the app * @param {Object} [options = {}] additional authentication options * @returns {Promise} */ }, { key: 'register', value: function register(email, password) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; return this.auth.provider('userpass').register(email, password, options); } /** * Links the currently logged in user with another identity. * * @param {String} providerType the provider of the other identity (e.g. 'userpass', 'facebook', 'google') * @param {Object} [options = {}] additional authentication options * @returns {Promise} which resolves to a String value: the original user ID */ }, { key: 'linkWithProvider', value: function linkWithProvider(providerType) { var _this = this; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (!this.isAuthenticated()) { throw new _errors.StitchError('Must be authenticated to link an account'); } return this.auth.provider(providerType).authenticate(options, true).then(function () { return _this.authedId(); }); } /** * Submits an authentication request to the specified provider providing any * included options (read: user data). If auth data already exists and the * existing auth data has an access token, then these credentials are returned. * * @param {String} providerType the provider used for authentication (The possible * options are 'anon', 'userpass', 'custom', 'facebook', 'google', * and 'apiKey') * @param {Object} [options = {}] additional authentication options * @returns {Promise} which resolves to a String value: the authenticated user ID */ }, { key: 'authenticate', value: function authenticate(providerType) { var _this2 = this; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; // reuse existing auth if present var authenticateFn = function authenticateFn() { return _this2.auth.provider(providerType).authenticate(options).then(function () { return _this2.authedId(); }); }; if (this.isAuthenticated()) { if (providerType === _providers.PROVIDER_TYPE_ANON && this.auth.getLoggedInProviderType() === _providers.PROVIDER_TYPE_ANON) { return Promise.resolve(this.auth.authedId); // is authenticated, skip log in } return this.logout().then(function () { return authenticateFn(); }); // will not be authenticated, continue log in } // is not authenticated, continue log in return authenticateFn(); } /** * Ends the session for the current user, and clears auth information from storage. * * @returns {Promise} */ }, { key: 'logout', value: function logout() { var _this3 = this; return this._do('/auth/session', 'DELETE', { refreshOnFailure: false, useRefreshToken: true, rootURL: this.rootURLsByAPIVersion[v2][API_TYPE_CLIENT] }).then(function () { return _this3.auth.clear(); }, function () { return _this3.auth.clear(); }); } /** * @returns {*} Returns any error from the Stitch authentication system. */ }, { key: 'authError', value: function authError() { return this.auth.error(); } /** * Returns profile information for the currently logged in user. * * @returns {Promise} which resolves to a a JSON object containing user profile information. */ }, { key: 'userProfile', value: function userProfile() { return this._do('/auth/profile', 'GET', { rootURL: this.rootURLsByAPIVersion[v2][API_TYPE_CLIENT] }).then(function (response) { return response.json(); }); } /** * @returns {Boolean} whether or not the current client is authenticated. */ }, { key: 'isAuthenticated', value: function isAuthenticated() { return !!this.authedId(); } /** * @returns {String} a string of the currently authenticated user's ID. */ }, { key: 'authedId', value: function authedId() { return this.auth.authedId; } /** * Factory method for accessing Stitch services. * * @method * @param {String} type the service type (e.g. "mongodb", "aws-s3", "aws-ses", "twilio", "http", etc.) * @param {String} name the service name specified in the Stitch admin console. * @returns {Object} returns an instance of the specified service type. */ }, { key: 'service', value: function service(type, name) { if (this.constructor !== StitchClient) { throw new _errors.StitchError('`service` is a factory method, do not use `new`'); } if (!_services2.default.hasOwnProperty(type)) { throw new _errors.StitchError('Invalid service type specified: ' + type); } var ServiceType = _services2.default[type]; return new ServiceType(this, name); } /** * Executes a function. * * @param {String} name The name of the function. * @param {...*} args Arguments to pass to the function. */ }, { key: 'executeFunction', value: function executeFunction(name) { for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } return this._doFunctionCall({ name: name, arguments: args }); } /** * Executes a service function. * * @param {String} service The name of the service. * @param {String} action The name of the service action. * @param {...*} args Arguments to pass to the service action. */ }, { key: 'executeServiceFunction', value: function executeServiceFunction(service, action) { for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) { args[_key2 - 2] = arguments[_key2]; } return this._doFunctionCall({ service: service, name: action, arguments: args }); } }, { key: '_doFunctionCall', value: function _doFunctionCall(request) { var responseDecoder = function responseDecoder(d) { return _mongodbExtjson2.default.parse(d, { relaxed: true }); }; var responseEncoder = function responseEncoder(d) { return _mongodbExtjson2.default.stringify(d, { strict: true }); }; return this._do('/functions/call', 'POST', { body: responseEncoder(request) }).then(function (response) { return response.text(); }).then(function (body) { return responseDecoder(body); }); } /** * Returns an access token for the user * * @private * @returns {Promise} */ }, { key: 'doSessionPost', value: function doSessionPost() { return this._do('/auth/session', 'POST', { refreshOnFailure: false, useRefreshToken: true, rootURL: this.rootURLsByAPIVersion[v2][API_TYPE_CLIENT] }).then(function (response) { return response.json(); }); } /** * Returns the user API keys associated with the current user. * * @returns {Promise} which resolves to an array of API key objects */ }, { key: 'getApiKeys', value: function getApiKeys() { return this._do('/auth/api_keys', 'GET', { rootURL: this.rootURLsByAPIVersion[v2][API_TYPE_CLIENT], useRefreshToken: true }).then(function (response) { return response.json(); }); } /** * Creates a user API key that can be used to authenticate as the current user. * * @param {String} userApiKeyName a unique name for the user API key * @returns {Promise} which resolves to an API key object containing the API key value */ }, { key: 'createApiKey', value: function createApiKey(userApiKeyName) { return this._do('/auth/api_keys', 'POST', { rootURL: this.rootURLsByAPIVersion[v2][API_TYPE_CLIENT], useRefreshToken: true, body: JSON.stringify({ 'name': userApiKeyName }) }).then(function (response) { return response.json(); }); } /** * Returns a user API key associated with the current user. * * @param {String} keyID the ID of the key to fetch * @returns {Promise} which resolves to an API key object, although the API key value will be omitted */ }, { key: 'getApiKeyByID', value: function getApiKeyByID(keyID) { return this._do('/auth/api_keys/' + keyID, 'GET', { rootURL: this.rootURLsByAPIVersion[v2][API_TYPE_CLIENT], useRefreshToken: true }).then(function (response) { return response.json(); }); } /** * Deletes a user API key associated with the current user. * * @param {String} keyID the ID of the key to delete * @returns {Promise} */ }, { key: 'deleteApiKeyByID', value: function deleteApiKeyByID(keyID) { return this._do('/auth/api_keys/' + keyID, 'DELETE', { rootURL: this.rootURLsByAPIVersion[v2][API_TYPE_CLIENT], useRefreshToken: true }); } /** * Enables a user API key associated with the current user. * * @param {String} keyID the ID of the key to enable * @returns {Promise} */ }, { key: 'enableApiKeyByID', value: function enableApiKeyByID(keyID) { return this._do('/auth/api_keys/' + keyID + '/enable', 'PUT', { rootURL: this.rootURLsByAPIVersion[v2][API_TYPE_CLIENT], useRefreshToken: true }); } /** * Disables a user API key associated with the current user. * * @param {String} keyID the ID of the key to disable * @returns {Promise} */ }, { key: 'disableApiKeyByID', value: function disableApiKeyByID(keyID) { return this._do('/auth/api_keys/' + keyID + '/disable', 'PUT', { rootURL: this.rootURLsByAPIVersion[v2][API_TYPE_CLIENT], useRefreshToken: true }); } }, { key: '_fetch', value: function _fetch(url, fetchArgs, resource, method, options) { var _this4 = this; return fetch(url, fetchArgs).then(function (response) { // Okay: passthrough if (response.status >= 200 && response.status < 300) { return Promise.resolve(response); } if (response.headers.get('Content-Type') === common.JSONTYPE) { return response.json().then(function (json) { // Only want to try refreshing token when there's an invalid session if ('error_code' in json && json.error_code === _errors.ErrInvalidSession) { if (!options.refreshOnFailure) { return _this4.auth.clear().then(function () { var error = new _errors.StitchError(json.error, json.error_code); error.response = response; error.json = json; throw error; }); } return _this4.auth.refreshToken().then(function () { options.refreshOnFailure = false; return _this4._do(resource, method, options); }); } var error = new _errors.StitchError(json.error, json.error_code); error.response = response; error.json = json; return Promise.reject(error); }); } var error = new Error(response.statusText); error.response = response; return Promise.reject(error); }); } }, { key: '_fetchArgs', value: function _fetchArgs(resource, method, options) { var appURL = this.rootURLsByAPIVersion[options.apiVersion][options.apiType]; var url = '' + appURL + resource; if (options.rootURL) { url = '' + options.rootURL + resource; } var fetchArgs = common.makeFetchArgs(method, options.body, options); if (!!options.headers) { Object.assign(fetchArgs.headers, options.headers); } if (options.queryParams) { url = url + '?' + _queryString2.default.stringify(options.queryParams); } if (options.multipart) { // fall-back on browser to generate Content-Type for us based on request body (FormData) delete fetchArgs.headers['Content-Type']; } return { url: url, fetchArgs: fetchArgs }; } }, { key: '_do', value: function _do(resource, method, options) { options = Object.assign({}, { refreshOnFailure: true, useRefreshToken: false, apiVersion: v2, apiType: API_TYPE_APP, rootURL: undefined }, options); var _fetchArgs2 = this._fetchArgs(resource, method, options), url = _fetchArgs2.url, fetchArgs = _fetchArgs2.fetchArgs; if (options.noAuth) { return this._fetch(url, fetchArgs, resource, method, options); } if (!this.isAuthenticated()) { return Promise.reject(new _errors.StitchError('Must auth first', _errors.ErrUnauthorized)); } if (this.auth.requestOrigin) { fetchArgs.headers['X-STITCH-Request-Origin'] = this.auth.requestOrigin; } var token = options.useRefreshToken ? this.auth.getRefreshToken() : this.auth.getAccessToken(); fetchArgs.headers.Authorization = 'Bearer ' + token; return this._fetch(url, fetchArgs, resource, method, options); } }, { key: 'type', get: function get() { return common.APP_CLIENT_TYPE; } }]); return StitchClient; }();