UNPKG

blockstack

Version:

The Blockstack Javascript library for authentication, identity, and storage.

1,273 lines (1,150 loc) 3.05 MB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.blockstack = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTransitKey = getTransitKey; exports.generateAndStoreTransitKey = generateAndStoreTransitKey; exports.isUserSignedIn = isUserSignedIn; exports.redirectToSignInWithAuthRequest = redirectToSignInWithAuthRequest; exports.redirectToSignIn = redirectToSignIn; exports.getAuthResponseToken = getAuthResponseToken; exports.isSignInPending = isSignInPending; exports.handlePendingSignIn = handlePendingSignIn; exports.loadUserData = loadUserData; exports.signUserOut = signUserOut; var _queryString = require('query-string'); var _queryString2 = _interopRequireDefault(_queryString); var _jsontokens = require('jsontokens'); var _index = require('./index'); var _utils = require('../utils'); var _index2 = require('../index'); var _errors = require('../errors'); var _authMessages = require('./authMessages'); var _authConstants = require('./authConstants'); var _storage = require('../storage'); var _profiles = require('../profiles'); var _logger = require('../logger'); var _config = require('../config'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var DEFAULT_PROFILE = { '@type': 'Person', '@context': 'http://schema.org' /** * Fetches the hex value of the transit private key from local storage. * @return {String} the hex encoded private key * @private */ };function getTransitKey() { var transitKey = localStorage.getItem(_authConstants.BLOCKSTACK_APP_PRIVATE_KEY_LABEL); return transitKey; } /** * Generates a ECDSA keypair to * use as the ephemeral app transit private key * and stores the hex value of the private key in * local storage. * @return {String} the hex encoded private key */ function generateAndStoreTransitKey() { var transitKey = (0, _index2.makeECPrivateKey)(); localStorage.setItem(_authConstants.BLOCKSTACK_APP_PRIVATE_KEY_LABEL, transitKey); return transitKey; } /** * Check if a user is currently signed in. * @return {Boolean} `true` if the user is signed in, `false` if not. */ function isUserSignedIn() { return !!window.localStorage.getItem(_authConstants.BLOCKSTACK_STORAGE_LABEL); } /** * Detects if the native auth-browser is installed and is successfully * launched via a custom protocol URI. * @param {String} authRequest * The encoded authRequest to be used as a query param in the custom URI. * @param {String} successCallback * The callback that is invoked when the protocol handler was detected. * @param {String} failCallback * The callback that is invoked when the protocol handler was not detected. * @return {void} */ function detectProtocolLaunch(authRequest, successCallback, failCallback) { // Create a unique ID used for this protocol detection attempt. var echoReplyID = Math.random().toString(36).substr(2, 9); var echoReplyKeyPrefix = 'echo-reply-'; var echoReplyKey = '' + echoReplyKeyPrefix + echoReplyID; // Use localStorage as a reliable cross-window communication method. // Create the storage entry to signal a protocol detection attempt for the // next browser window to check. window.localStorage.setItem(echoReplyKey, Date.now().toString()); var cleanUpLocalStorage = function cleanUpLocalStorage() { try { window.localStorage.removeItem(echoReplyKey); // Also clear out any stale echo-reply keys older than 1 hour. for (var i = 0; i < window.localStorage.length; i++) { var storageKey = window.localStorage.key(i); if (storageKey.startsWith(echoReplyKeyPrefix)) { var storageValue = window.localStorage.getItem(storageKey); if (storageValue === 'success' || Date.now() - parseInt(storageValue, 10) > 3600000) { window.localStorage.removeItem(storageKey); } } } } catch (err) { _logger.Logger.error('Exception cleaning up echo-reply entries in localStorage'); _logger.Logger.error(err); } }; var detectionTimeout = 1000; var redirectToWebAuthTimer = 0; var cancelWebAuthRedirectTimer = function cancelWebAuthRedirectTimer() { if (redirectToWebAuthTimer) { window.clearTimeout(redirectToWebAuthTimer); redirectToWebAuthTimer = 0; } }; var startWebAuthRedirectTimer = function startWebAuthRedirectTimer() { var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : detectionTimeout; cancelWebAuthRedirectTimer(); redirectToWebAuthTimer = window.setTimeout(function () { if (redirectToWebAuthTimer) { cancelWebAuthRedirectTimer(); var nextFunc = void 0; if (window.localStorage.getItem(echoReplyKey) === 'success') { _logger.Logger.info('Protocol echo reply detected.'); nextFunc = successCallback; } else { _logger.Logger.info('Protocol handler not detected.'); nextFunc = failCallback; } failCallback = function failCallback() {}; successCallback = function successCallback() {}; cleanUpLocalStorage(); // Briefly wait since localStorage changes can // sometimes be ignored when immediately redirected. setTimeout(function () { return nextFunc(); }, 100); } }, timeout); }; startWebAuthRedirectTimer(); var inputPromptTracker = document.createElement('input'); inputPromptTracker.type = 'text'; // Prevent this element from inherited any css. inputPromptTracker.style.all = 'initial'; // Setting display=none on an element prevents them from being focused/blurred. // So hide the element using other properties.. inputPromptTracker.style.opacity = '0'; inputPromptTracker.style.filter = 'alpha(opacity=0)'; inputPromptTracker.style.height = '0'; inputPromptTracker.style.width = '0'; // If the the focus of a page element is immediately changed then this likely indicates // the protocol handler is installed, and the browser is prompting the user if they want // to open the application. var inputBlurredFunc = function inputBlurredFunc() { // Use a timeout of 100ms to ignore instant toggles between blur and focus. // Browsers often perform an instant blur & focus when the protocol handler is working // but not showing any browser prompts, so we want to ignore those instances. var isRefocused = false; inputPromptTracker.addEventListener('focus', function () { isRefocused = true; }, { once: true, capture: true }); setTimeout(function () { if (redirectToWebAuthTimer && !isRefocused) { _logger.Logger.info('Detected possible browser prompt for opening the protocol handler app.'); window.clearTimeout(redirectToWebAuthTimer); inputPromptTracker.addEventListener('focus', function () { if (redirectToWebAuthTimer) { _logger.Logger.info('Possible browser prompt closed, restarting auth redirect timeout.'); startWebAuthRedirectTimer(); } }, { once: true, capture: true }); } }, 100); }; inputPromptTracker.addEventListener('blur', inputBlurredFunc, { once: true, capture: true }); setTimeout(function () { return inputPromptTracker.removeEventListener('blur', inputBlurredFunc); }, 200); // Flow complains without this check. if (document.body) document.body.appendChild(inputPromptTracker); inputPromptTracker.focus(); // Detect if document.visibility is immediately changed which is a strong // indication that the protocol handler is working. We don't know for sure and // can't predict future browser changes, so only increase the redirect timeout. // This reduces the probability of a false-negative (where local auth works, but // the original page was redirect to web auth because something took too long), var pageVisibilityChanged = function pageVisibilityChanged() { if (document.hidden && redirectToWebAuthTimer) { _logger.Logger.info('Detected immediate page visibility change (protocol handler probably working).'); startWebAuthRedirectTimer(3000); } }; document.addEventListener('visibilitychange', pageVisibilityChanged, { once: true, capture: true }); setTimeout(function () { return document.removeEventListener('visibilitychange', pageVisibilityChanged); }, 500); // Listen for the custom protocol echo reply via localStorage update event. window.addEventListener('storage', function replyEventListener(event) { if (event.key === echoReplyKey && window.localStorage.getItem(echoReplyKey) === 'success') { // Custom protocol worked, cancel the web auth redirect timer. cancelWebAuthRedirectTimer(); inputPromptTracker.removeEventListener('blur', inputBlurredFunc); _logger.Logger.info('Protocol echo reply detected from localStorage event.'); // Clean up event listener and localStorage. window.removeEventListener('storage', replyEventListener); var nextFunc = successCallback; successCallback = function successCallback() {}; failCallback = function failCallback() {}; cleanUpLocalStorage(); // Briefly wait since localStorage changes can sometimes // be ignored when immediately redirected. setTimeout(function () { return nextFunc(); }, 100); } }, false); // Use iframe technique for launching the protocol URI rather than setting `window.location`. // This method prevents browsers like Safari, Opera, Firefox from showing error prompts // about unknown protocol handler when app is not installed, and avoids an empty // browser tab when the app is installed. _logger.Logger.info('Attempting protocol launch via iframe injection.'); var locationSrc = _utils.BLOCKSTACK_HANDLER + ':' + authRequest + '&echo=' + echoReplyID; var iframe = document.createElement('iframe'); iframe.style.all = 'initial'; iframe.style.display = 'none'; iframe.src = locationSrc; // Flow complains without this check. if (document.body) { document.body.appendChild(iframe); } else { _logger.Logger.error('document.body is null when attempting iframe injection for protoocol URI launch'); } } /** * Redirects the user to the Blockstack browser to approve the sign in request * given. * * The user is redirected to the `blockstackIDHost` if the `blockstack:` * protocol handler is not detected. Please note that the protocol handler detection * does not work on all browsers. * @param {String} authRequest - the authentication request generated by `makeAuthRequest` * @param {String} blockstackIDHost - the URL to redirect the user to if the blockstack * protocol handler is not detected * @return {void} */ function redirectToSignInWithAuthRequest() { var authRequest = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : (0, _index.makeAuthRequest)(); var blockstackIDHost = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _authConstants.DEFAULT_BLOCKSTACK_HOST; var httpsURI = blockstackIDHost + '?authRequest=' + authRequest; // If they're on a mobile OS, always redirect them to HTTPS site if (/Android|webOS|iPhone|iPad|iPod|Opera Mini/i.test(navigator.userAgent)) { _logger.Logger.info('detected mobile OS, sending to https'); window.location = httpsURI; return; } function successCallback() { _logger.Logger.info('protocol handler detected'); // The detection function should open the link for us } function failCallback() { _logger.Logger.warn('protocol handler not detected'); window.location = httpsURI; } detectProtocolLaunch(authRequest, successCallback, failCallback); } /** * Generates an authentication request and redirects the user to the Blockstack * browser to approve the sign in request. * * Please note that this requires that the web browser properly handles the * `blockstack:` URL protocol handler. * * Most applications should use this * method for sign in unless they require more fine grained control over how the * authentication request is generated. If your app falls into this category, * use `makeAuthRequest` and `redirectToSignInWithAuthRequest` to build your own sign in process. * * @param {String} [redirectURI=`${window.location.origin}/`] * The location to which the identity provider will redirect the user after * the user approves sign in. * @param {String} [manifestURI=`${window.location.origin}/manifest.json`] * Location of the manifest file. * @param {Array} [scopes=DEFAULT_SCOPE] Defaults to requesting write access to * this app's data store. * An array of strings indicating which permissions this app is requesting. * @return {void} */ function redirectToSignIn() { var redirectURI = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.location.origin + '/'; var manifestURI = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : window.location.origin + '/manifest.json'; var scopes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _authConstants.DEFAULT_SCOPE; var authRequest = (0, _index.makeAuthRequest)(generateAndStoreTransitKey(), redirectURI, manifestURI, scopes); redirectToSignInWithAuthRequest(authRequest); } /** * Retrieve the authentication token from the URL query * @return {String} the authentication token if it exists otherwise `null` */ function getAuthResponseToken() { var queryDict = _queryString2.default.parse(location.search); return queryDict.authResponse ? queryDict.authResponse : ''; } /** * Check if there is a authentication request that hasn't been handled. * @return {Boolean} `true` if there is a pending sign in, otherwise `false` */ function isSignInPending() { return !!getAuthResponseToken(); } /** * Try to process any pending sign in request by returning a `Promise` that resolves * to the user data object if the sign in succeeds. * * @param {String} nameLookupURL - the endpoint against which to verify public * keys match claimed username * @param {String} authResponseToken - the signed authentication response token * @param {String} transitKey - the transit private key that corresponds to the transit public key * that was provided in the authentication request * @return {Promise} that resolves to the user data object if successful and rejects * if handling the sign in request fails or there was no pending sign in request. */ function handlePendingSignIn() { var nameLookupURL = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; var authResponseToken = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : getAuthResponseToken(); var transitKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : getTransitKey(); if (!nameLookupURL) { var tokenPayload = (0, _jsontokens.decodeToken)(authResponseToken).payload; if ((0, _utils.isLaterVersion)(tokenPayload.version, '1.3.0') && tokenPayload.blockstackAPIUrl !== null && tokenPayload.blockstackAPIUrl !== undefined) { // override globally _logger.Logger.info('Overriding ' + _config.config.network.blockstackAPIUrl + ' ' + ('with ' + tokenPayload.blockstackAPIUrl)); _config.config.network.blockstackAPIUrl = tokenPayload.blockstackAPIUrl; nameLookupURL = tokenPayload.blockstackAPIUrl + '/v1/names'; } nameLookupURL = _config.config.network.blockstackAPIUrl + '/v1/names/'; } return (0, _index.verifyAuthResponse)(authResponseToken, nameLookupURL).then(function (isValid) { if (!isValid) { throw new _errors.LoginFailedError('Invalid authentication response.'); } var tokenPayload = (0, _jsontokens.decodeToken)(authResponseToken).payload; // TODO: real version handling var appPrivateKey = tokenPayload.private_key; var coreSessionToken = tokenPayload.core_token; if ((0, _utils.isLaterVersion)(tokenPayload.version, '1.1.0')) { if (transitKey !== undefined && transitKey != null) { if (tokenPayload.private_key !== undefined && tokenPayload.private_key !== null) { try { appPrivateKey = (0, _authMessages.decryptPrivateKey)(transitKey, tokenPayload.private_key); } catch (e) { _logger.Logger.warn('Failed decryption of appPrivateKey, will try to use as given'); try { (0, _utils.hexStringToECPair)(tokenPayload.private_key); } catch (ecPairError) { throw new _errors.LoginFailedError('Failed decrypting appPrivateKey. Usually means' + ' that the transit key has changed during login.'); } } } if (coreSessionToken !== undefined && coreSessionToken !== null) { try { coreSessionToken = (0, _authMessages.decryptPrivateKey)(transitKey, coreSessionToken); } catch (e) { _logger.Logger.info('Failed decryption of coreSessionToken, will try to use as given'); } } } else { throw new _errors.LoginFailedError('Authenticating with protocol > 1.1.0 requires transit' + ' key, and none found.'); } } var hubUrl = _authConstants.BLOCKSTACK_DEFAULT_GAIA_HUB_URL; var gaiaAssociationToken = void 0; if ((0, _utils.isLaterVersion)(tokenPayload.version, '1.2.0') && tokenPayload.hubUrl !== null && tokenPayload.hubUrl !== undefined) { hubUrl = tokenPayload.hubUrl; } if ((0, _utils.isLaterVersion)(tokenPayload.version, '1.3.0') && tokenPayload.associationToken !== null && tokenPayload.associationToken !== undefined) { gaiaAssociationToken = tokenPayload.associationToken; } var userData = { username: tokenPayload.username, profile: tokenPayload.profile, decentralizedID: tokenPayload.iss, identityAddress: (0, _index2.getAddressFromDID)(tokenPayload.iss), appPrivateKey: appPrivateKey, coreSessionToken: coreSessionToken, authResponseToken: authResponseToken, hubUrl: hubUrl, gaiaAssociationToken: gaiaAssociationToken }; var profileURL = tokenPayload.profile_url; if ((userData.profile === null || userData.profile === undefined) && profileURL !== undefined && profileURL !== null) { return fetch(profileURL).then(function (response) { if (!response.ok) { // return blank profile if we fail to fetch userData.profile = Object.assign({}, DEFAULT_PROFILE); window.localStorage.setItem(_authConstants.BLOCKSTACK_STORAGE_LABEL, JSON.stringify(userData)); return userData; } else { return response.text().then(function (responseText) { return JSON.parse(responseText); }).then(function (wrappedProfile) { return (0, _profiles.extractProfile)(wrappedProfile[0].token); }).then(function (profile) { userData.profile = profile; window.localStorage.setItem(_authConstants.BLOCKSTACK_STORAGE_LABEL, JSON.stringify(userData)); return userData; }); } }); } else { userData.profile = tokenPayload.profile; window.localStorage.setItem(_authConstants.BLOCKSTACK_STORAGE_LABEL, JSON.stringify(userData)); return userData; } }); } /** * Retrieves the user data object. The user's profile is stored in the key `profile`. * @return {Object} User data object. */ function loadUserData() { return JSON.parse(window.localStorage.getItem(_authConstants.BLOCKSTACK_STORAGE_LABEL)); } /** * Sign the user out and optionally redirect to given location. * @param {String} [redirectURL=null] Location to redirect user to after sign out. * @return {void} */ function signUserOut() { var redirectURL = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; window.localStorage.removeItem(_authConstants.BLOCKSTACK_STORAGE_LABEL); window.localStorage.removeItem(_storage.BLOCKSTACK_GAIA_HUB_LABEL); if (redirectURL !== null) { window.location = redirectURL; } } },{"../config":8,"../errors":11,"../index":12,"../logger":14,"../profiles":22,"../storage":45,"../utils":46,"./authConstants":2,"./authMessages":3,"./index":7,"jsontokens":383,"query-string":449}],2:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var BLOCKSTACK_HANDLER = exports.BLOCKSTACK_HANDLER = 'blockstack'; var BLOCKSTACK_STORAGE_LABEL = exports.BLOCKSTACK_STORAGE_LABEL = 'blockstack'; var DEFAULT_BLOCKSTACK_HOST = exports.DEFAULT_BLOCKSTACK_HOST = 'https://browser.blockstack.org/auth'; var DEFAULT_SCOPE = exports.DEFAULT_SCOPE = ['store_write']; var BLOCKSTACK_APP_PRIVATE_KEY_LABEL = exports.BLOCKSTACK_APP_PRIVATE_KEY_LABEL = 'blockstack-transit-private-key'; var BLOCKSTACK_DEFAULT_GAIA_HUB_URL = exports.BLOCKSTACK_DEFAULT_GAIA_HUB_URL = 'https://hub.blockstack.org'; },{}],3:[function(require,module,exports){ (function (Buffer){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeAuthRequest = makeAuthRequest; exports.encryptPrivateKey = encryptPrivateKey; exports.decryptPrivateKey = decryptPrivateKey; exports.makeAuthResponse = makeAuthResponse; require('cross-fetch/polyfill'); var _jsontokens = require('jsontokens'); var _index = require('../index'); var _authConstants = require('./authConstants'); var _encryption = require('../encryption'); var _logger = require('../logger'); var VERSION = '1.3.1'; /** * Generates an authentication request that can be sent to the Blockstack * browser for the user to approve sign in. This authentication request can * then be used for sign in by passing it to the `redirectToSignInWithAuthRequest` * method. * * *Note: This method should only be used if you want to roll your own authentication * flow. Typically you'd use `redirectToSignIn` which takes care of this * under the hood.* * * @param {String} [transitPrivateKey=generateAndStoreTransitKey()] - hex encoded transit * private key * @param {String} redirectURI - location to redirect user to after sign in approval * @param {String} manifestURI - location of this app's manifest file * @param {Array<String>} scopes - the permissions this app is requesting * @param {String} appDomain - the origin of this app * @param {Number} expiresAt - the time at which this request is no longer valid * @param {Object} extraParams - Any extra parameters you'd like to pass to the authenticator. * Use this to pass options that aren't part of the Blockstack auth spec, but might be supported * by special authenticators. * @return {String} the authentication request */ function makeAuthRequest() { var transitPrivateKey = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : (0, _index.generateAndStoreTransitKey)(); var redirectURI = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : window.location.origin + '/'; var manifestURI = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : window.location.origin + '/manifest.json'; var scopes = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : _authConstants.DEFAULT_SCOPE; var appDomain = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : window.location.origin; var expiresAt = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : (0, _index.nextHour)().getTime(); var extraParams = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : {}; /* Create the payload */ var payload = Object.assign({}, extraParams, { jti: (0, _index.makeUUID4)(), iat: Math.floor(new Date().getTime() / 1000), // JWT times are in seconds exp: Math.floor(expiresAt / 1000), // JWT times are in seconds iss: null, public_keys: [], domain_name: appDomain, manifest_uri: manifestURI, redirect_uri: redirectURI, version: VERSION, do_not_include_profile: true, supports_hub_url: true, scopes: scopes }); _logger.Logger.info('blockstack.js: generating v' + VERSION + ' auth request'); /* Convert the private key to a public key to an issuer */ var publicKey = _jsontokens.SECP256K1Client.derivePublicKey(transitPrivateKey); payload.public_keys = [publicKey]; var address = (0, _index.publicKeyToAddress)(publicKey); payload.iss = (0, _index.makeDIDFromAddress)(address); /* Sign and return the token */ var tokenSigner = new _jsontokens.TokenSigner('ES256k', transitPrivateKey); var token = tokenSigner.sign(payload); return token; } /** * Encrypts the private key for decryption by the given * public key. * @param {String} publicKey [description] * @param {String} privateKey [description] * @return {String} hex encoded ciphertext * @private */ function encryptPrivateKey(publicKey, privateKey) { var encryptedObj = (0, _encryption.encryptECIES)(publicKey, privateKey); var encryptedJSON = JSON.stringify(encryptedObj); return new Buffer(encryptedJSON).toString('hex'); } /** * Decrypts the hex encrypted private key * @param {String} privateKey the private key corresponding to the public * key for which the ciphertext was encrypted * @param {String} hexedEncrypted the ciphertext * @return {String} the decrypted private key * @throws {Error} if unable to decrypt * * @private */ function decryptPrivateKey(privateKey, hexedEncrypted) { var unhexedString = new Buffer(hexedEncrypted, 'hex').toString(); var encryptedObj = JSON.parse(unhexedString); var decrypted = (0, _encryption.decryptECIES)(privateKey, encryptedObj); if (typeof decrypted !== 'string') { throw new Error('Unable to correctly decrypt private key'); } else { return decrypted; } } /** * Generates a signed authentication response token for an app. This * token is sent back to apps which use contents to access the * resources and data requested by the app. * * @param {String} privateKey the identity key of the Blockstack ID generating * the authentication response * @param {Object} profile the profile object for the Blockstack ID * @param {String} username the username of the Blockstack ID if any, otherwise `null` * @param {AuthMetadata} metadata an object containing metadata sent as part of the authentication * response including `email` if requested and available and a URL to the profile * @param {String} coreToken core session token when responding to a legacy auth request * or `null` for current direct to gaia authentication requests * @param {String} appPrivateKey the application private key. This private key is * unique and specific for every Blockstack ID and application combination. * @param {Number} expiresAt an integer in the same format as * `new Date().getTime()`, milliseconds since the Unix epoch * @param {String} transitPublicKey the public key provide by the app * in its authentication request with which secrets will be encrypted * @param {String} hubUrl URL to the write path of the user's Gaia hub * @param {String} blockstackAPIUrl URL to the API endpoint to use * @param {String} associationToken JWT that binds the app key to the identity key * @return {String} signed and encoded authentication response token * @private */ function makeAuthResponse(privateKey) { var profile = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var username = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; var metadata = arguments[3]; var coreToken = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null; var appPrivateKey = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null; var expiresAt = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : (0, _index.nextMonth)().getTime(); var transitPublicKey = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : null; var hubUrl = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : null; var blockstackAPIUrl = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : null; var associationToken = arguments.length > 10 && arguments[10] !== undefined ? arguments[10] : null; /* Convert the private key to a public key to an issuer */ var publicKey = _jsontokens.SECP256K1Client.derivePublicKey(privateKey); var address = (0, _index.publicKeyToAddress)(publicKey); /* See if we should encrypt with the transit key */ var privateKeyPayload = appPrivateKey; var coreTokenPayload = coreToken; var additionalProperties = {}; if (appPrivateKey !== undefined && appPrivateKey !== null) { _logger.Logger.info('blockstack.js: generating v' + VERSION + ' auth response'); if (transitPublicKey !== undefined && transitPublicKey !== null) { privateKeyPayload = encryptPrivateKey(transitPublicKey, appPrivateKey); if (coreToken !== undefined && coreToken !== null) { coreTokenPayload = encryptPrivateKey(transitPublicKey, coreToken); } } additionalProperties = { email: metadata.email ? metadata.email : null, profile_url: metadata.profileUrl ? metadata.profileUrl : null, hubUrl: hubUrl, blockstackAPIUrl: blockstackAPIUrl, associationToken: associationToken, version: VERSION }; } else { _logger.Logger.info('blockstack.js: generating legacy auth response'); } /* Create the payload */ var payload = Object.assign({}, { jti: (0, _index.makeUUID4)(), iat: Math.floor(new Date().getTime() / 1000), // JWT times are in seconds exp: Math.floor(expiresAt / 1000), // JWT times are in seconds iss: (0, _index.makeDIDFromAddress)(address), private_key: privateKeyPayload, public_keys: [publicKey], profile: profile, username: username, core_token: coreTokenPayload }, additionalProperties); /* Sign and return the token */ var tokenSigner = new _jsontokens.TokenSigner('ES256k', privateKey); return tokenSigner.sign(payload); } }).call(this,require("buffer").Buffer) },{"../encryption":10,"../index":12,"../logger":14,"./authConstants":2,"buffer":179,"cross-fetch/polyfill":283,"jsontokens":383}],4:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAuthRequestFromURL = getAuthRequestFromURL; exports.fetchAppManifest = fetchAppManifest; exports.redirectUserToApp = redirectUserToApp; var _queryString = require('query-string'); var _queryString2 = _interopRequireDefault(_queryString); var _jsontokens = require('jsontokens'); var _index = require('../index'); var _utils = require('../utils'); var _logger = require('../logger'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Retrieves the authentication request from the query string * @return {String|null} the authentication request or `null` if * the query string parameter `authRequest` is not found * @private */ function getAuthRequestFromURL() { var queryDict = _queryString2.default.parse(location.search); if (queryDict.authRequest !== null && queryDict.authRequest !== undefined) { return queryDict.authRequest.split(_utils.BLOCKSTACK_HANDLER + ':').join(''); } else { return null; } } /** * Fetches the contents of the manifest file specified in the authentication request * * @param {String} authRequest encoded and signed authentication request * @return {Promise<Object|String>} Returns a `Promise` that resolves to the JSON * object manifest file unless there's an error in which case rejects with an error * message. * @private */ function fetchAppManifest(authRequest) { return new Promise(function (resolve, reject) { if (!authRequest) { reject('Invalid auth request'); } else { var payload = (0, _jsontokens.decodeToken)(authRequest).payload; var manifestURI = payload.manifest_uri; try { _logger.Logger.debug('Fetching manifest from ' + manifestURI); fetch(manifestURI).then(function (response) { return response.text(); }).then(function (responseText) { return JSON.parse(responseText); }).then(function (responseJSON) { resolve(responseJSON); }).catch(function (e) { _logger.Logger.debug(e.stack); reject('Could not fetch manifest.json'); }); } catch (e) { _logger.Logger.debug(e.stack); reject('Could not fetch manifest.json'); } } }); } /** * Redirect the user's browser to the app using the `redirect_uri` * specified in the authentication request, passing the authentication * response token as a query parameter. * * @param {String} authRequest encoded and signed authentication request token * @param {String} authResponse encoded and signed authentication response token * @return {void} * @throws {Error} if there is no redirect uri * @private */ function redirectUserToApp(authRequest, authResponse) { var payload = (0, _jsontokens.decodeToken)(authRequest).payload; var redirectURI = payload.redirect_uri; _logger.Logger.debug(redirectURI); if (redirectURI) { redirectURI = (0, _index.updateQueryStringParameter)(redirectURI, 'authResponse', authResponse); } else { throw new Error('Invalid redirect URI'); } window.location = redirectURI; } },{"../index":12,"../logger":14,"../utils":46,"jsontokens":383,"query-string":449}],5:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeCoreSessionRequest = makeCoreSessionRequest; exports.sendCoreSessionRequest = sendCoreSessionRequest; exports.getCoreSession = getCoreSession; var _jsontokens = require('jsontokens'); require('cross-fetch/polyfill'); /** * Create an authentication token to be sent to the Core API server * in order to generate a Core session JWT. * * @param {String} appDomain The unique application identifier (e.g. foo.app, www.foo.com, etc). * @param {Array} appMethods The list of API methods this application will need. * @param {String} appPrivateKey The application-specific private key * @param {String|null} blockchainID This is the blockchain ID of the requester * @param {String} thisDevice Identifier of the current device * * @return {String} a JWT signed by the app's private key * @deprecated * @private */ function makeCoreSessionRequest(appDomain, appMethods, appPrivateKey) { var blockchainID = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; var thisDevice = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null; if (thisDevice === null) { thisDevice = '.default'; } var appPublicKey = _jsontokens.SECP256K1Client.derivePublicKey(appPrivateKey); var appPublicKeys = [{ public_key: appPublicKey, device_id: thisDevice }]; var authBody = { version: 1, blockchain_id: blockchainID, app_private_key: appPrivateKey, app_domain: appDomain, methods: appMethods, app_public_keys: appPublicKeys, device_id: thisDevice // make token };var tokenSigner = new _jsontokens.TokenSigner('ES256k', appPrivateKey); var token = tokenSigner.sign(authBody); return token; } /** * Send Core a request for a session token. * * @param {String} coreHost host name of the core node * @param {Number} corePort port number of the core node * @param {String} coreAuthRequest a signed JWT encoding the authentication request * @param {String} apiPassword the API password for Core * * @return {Promise} the resolves to a JWT signed with the Core API server's private key * that authorizes the bearer to carry out the requested operations and rejects * with an error message otherwise * @deprecated * @private */ function sendCoreSessionRequest(coreHost, corePort, coreAuthRequest, apiPassword) { return new Promise(function (resolve, reject) { if (!apiPassword) { reject('Missing API password'); return null; } var options = { headers: { Authorization: 'bearer ' + apiPassword } }; var url = 'http://' + coreHost + ':' + corePort + '/v1/auth?authRequest=' + coreAuthRequest; return fetch(url, options).then(function (response) { if (!response.ok) { reject('HTTP status not OK'); throw new Error('HTTP status not OK'); } return response.text(); }).then(function (responseText) { return JSON.parse(responseText); }).then(function (responseJson) { var token = responseJson.token; if (!token) { reject('Failed to get Core session token'); return null; } resolve(token); return token; }).catch(function (error) { console.error(error); reject('Invalid Core response: not JSON'); }); }); } /** * Get a core session token. Generate an auth request, sign it, send it to Core, * and get back a session token. * * @param {String} coreHost Core API server's hostname * @param {Number} corePort Core API server's port number * @param {String} apiPassword core api password * @param {String} appPrivateKey Application's private key * @param {String} blockchainId blockchain ID of the user signing in. * `null` if user has no blockchain ID * @param {String} authRequest authentication request token * @param {String} deviceId identifier for the current device * * @return {Promise} a Promise that resolves to a Core session token or rejects * with an error message. * @deprecated * @private */ function getCoreSession(coreHost, corePort, apiPassword, appPrivateKey) { var blockchainId = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null; var authRequest = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null; var deviceId = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : '0'; if (!authRequest) { return Promise.reject('No authRequest provided'); } var payload = null; var authRequestObject = null; try { authRequestObject = (0, _jsontokens.decodeToken)(authRequest); if (!authRequestObject) { return Promise.reject('Invalid authRequest in URL query string'); } if (!authRequestObject.payload) { return Promise.reject('Invalid authRequest in URL query string'); } payload = authRequestObject.payload; } catch (e) { console.error(e.stack); return Promise.reject('Failed to parse authRequest in URL'); } var appDomain = payload.domain_name; if (!appDomain) { return Promise.reject('No domain_name in authRequest'); } var appMethods = payload.scopes; var coreAuthRequest = makeCoreSessionRequest(appDomain, appMethods, appPrivateKey, blockchainId, deviceId); return sendCoreSessionRequest(coreHost, corePort, coreAuthRequest, apiPassword); } },{"cross-fetch/polyfill":283,"jsontokens":383}],6:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.doSignaturesMatchPublicKeys = doSignaturesMatchPublicKeys; exports.doPublicKeysMatchIssuer = doPublicKeysMatchIssuer; exports.doPublicKeysMatchUsername = doPublicKeysMatchUsername; exports.isIssuanceDateValid = isIssuanceDateValid; exports.isExpirationDateValid = isExpirationDateValid; exports.isManifestUriValid = isManifestUriValid; exports.isRedirectUriValid = isRedirectUriValid; exports.verifyAuthRequest = verifyAuthRequest; exports.verifyAuthRequestAndLoadManifest = verifyAuthRequestAndLoadManifest; exports.verifyAuthResponse = verifyAuthResponse; var _jsontokens = require('jsontokens'); var _index = require('../index'); /** * Checks if the ES256k signature on passed `token` match the claimed public key * in the payload key `public_keys`. * * @param {String} token encoded and signed authentication token * @return {Boolean} Returns `true` if the signature matches the claimed public key * @throws {Error} if `token` contains multiple public keys * @private */ function doSignaturesMatchPublicKeys(token) { var payload = (0, _jsontokens.decodeToken)(token).payload; var publicKeys = payload.public_keys; if (publicKeys.length === 1) { var publicKey = publicKeys[0]; try { var tokenVerifier = new _jsontokens.TokenVerifier('ES256k', publicKey); var signatureVerified = tokenVerifier.verify(token); if (signatureVerified) { return true; } else { return false; } } catch (e) { return false; } } else { throw new Error('Multiple public keys are not supported'); } } /** * Makes sure that the identity address portion of * the decentralized identifier passed in the issuer `iss` * key of the token matches the public key * * @param {String} token encoded and signed authentication token * @return {Boolean} if the identity address and public keys match * @throws {Error} if ` token` has multiple public keys * @private */ function doPublicKeysMatchIssuer(token) { var payload = (0, _jsontokens.decodeToken)(token).payload; var publicKeys = payload.public_keys; var addressFromIssuer = (0, _index.getAddressFromDID)(payload.iss); if (publicKeys.length === 1) { var addressFromPublicKeys = (0, _index.publicKeyToAddress)(publicKeys[0]); if (addressFromPublicKeys === addressFromIssuer) { return true; } } else { throw new Error('Multiple public keys are not supported'); } return false; } /** * Looks up the identity address that owns the claimed username * in `token` using the lookup endpoint provided in `nameLookupURL` * to determine if the username is owned by the identity address * that matches the claimed public key * * @param {String} token encoded and signed authentication token * @param {String} nameLookupURL a URL to the name lookup endpoint of the Blockstack Core API * @return {Promise<Boolean>} returns a `Promise` that resolves to * `true` if the username is owned by the public key, otherwise the * `Promise` resolves to `false` * @private */ function doPublicKeysMatchUsername(token, nameLookupURL) { return new Promise(function (resolve) { var payload = (0, _jsontokens.decodeToken)(token).payload; if (!payload.username) { resolve(true); return; } if (payload.username === null) { resolve(true); return; } if (nameLookupURL === null) { resolve(false); return; } var username = payload.username; var url = nameLookupURL.replace(/\/$/, '') + '/' + username; try { fetch(url).then(function (response) { return response.text(); }).then(function (responseText) { return JSON.parse(responseText); }).then(function (responseJSON) { if (responseJSON.hasOwnProperty('address')) { var nameOwningAddress = responseJSON.address; var addressFromIssuer = (0, _index.getAddressFromDID)(payload.iss); if (nameOwningAddress === addressFromIssuer) { resolve(true); } else { resolve(false); } } else { resolve(false); } }).catch(function () { resolve(false); }); } catch (e) { resolve(false); } }); } /** * Checks if the if the token issuance time and date is after the * current time and date. * * @param {String} token encoded and signed authentication token * @return {Boolean} `true` if the token was issued after the current time, * otherwise returns `false` * @private */ function isIssuanceDateValid(token) { var payload = (0, _jsontokens.decodeToken)(token).payload; if (payload.iat) { if (typeof payload.iat !== 'number') { return false; } var issuedAt = new Date(payload.iat * 1000); // JWT times are in seconds if (new Date().getTime() < issuedAt.getTime()) { return false; } else { return true; } } else { return true; } } /** * Checks if the expiration date of the `token` is before the current time * @param {String} token encoded and signed authentication token * @return {Boolean} `true` if the `token` has not yet expired, `false` * if the `token` has expired * * @private */ function isExpirationDateValid(token) { var payload = (0, _jsontokens.decodeToken)(token).payload; if (payload.exp) { if (typeof payload.exp !== 'number') { return false; } var expiresAt = new Date(payload.exp * 1000); // JWT times are in seconds if (new Date().getTime() > expiresAt.getTime()) { return false; } else { return true; } } else { return true; } } /** * Makes sure the `manifest_uri` is a same origin absolute URL. * @param {String} token encoded and signed authentication token * @return {Boolean} `true` if valid, otherwise `false` * @private */ function isManifestUriValid(token) { var payload = (0, _jsontokens.decodeToken)(token).payload; return (0, _index.isSameOriginAbsoluteUrl)(payload.domain_name, payload.manifest_uri); } /** * Makes sure the `redirect_uri` is a same origin absolute URL. * @param {String} token encoded and signed authentication token * @return {Boolean} `true` if valid, otherwise `false` * @private */ function isRedirectUriValid(token) { var payload = (0, _jsontokens.decodeToken)(token).payload; return (0, _index.isSameOriginAbsoluteUrl)(payload.domain_name, payload.redirect_uri); } /** * Verify authentication request is valid. This function performs a number * of checks on the authentication request token: * * Checks that `token` has a valid issuance date & is not expired * * Checks that `token` has a valid signature that matches the public key it claims * * Checks that both the manifest and redirect URLs are absolute and conform to * the same origin policy * * @param {String} token encoded and signed authentication request token * @return {Promise} that resolves to true if the auth request * is valid and false if it does not. It rejects with a String if the * token is not signed * @private */ function verifyAuthRequest(token) { return new Promise(function (resolve, reject) { if ((0, _jsontokens.decodeToken)(token).header.alg === 'none') { reject('Token must be signed in order to be verified'); } Promise.all([isExpirationDateValid(token), isIssuanceDateValid(token), doSignaturesMatchPublicKeys(token), doPublicKeysMatchIssuer(token), isManifestUriValid(token), isRedirectUriValid(token)]).then(function (values) { if (values.every(Boolean)) { resolve(true); } else { resolve(false); } }); }); } /** * Verify the authentication request is valid and * fetch the app manifest file if valid. Otherwise, reject the promise. * @param {String} token encoded and signed authentication request token * @return {Promise} that resolves to the app manifest file in JSON format * or rejects if the auth request or app manifest file is invalid * @private */ function verifyAuthRequestAndLoadManifest(token) { return new Promise(function (resolve, reject) { return verifyAuthRequest(token).then(function (valid) { if (valid) { return (0, _index.fetchAppManifest)(token).then(function (appManifest) { resolve(appManifest); }).catch(function (err) { reject(err); }); } else { reject(); return Promise.reject(); } }); }); } /** * Verify the authentication response is valid * @param {String} token the authentication response token * @param {String} nameLookupURL the url use to verify owner of a username * @return {Promise} that resolves to true if auth response * is valid and false if it does not * @private */ function verifyAuthResponse(token, nameLookupURL) { return new Promise(function (resolve) { Promise.all([isExpirationDateValid(token), isIssuanceDateValid(token), doSignaturesMatchPublicKeys(token), doPublicKeysMatchIssuer(token), doPublicKeysMatchUsername(token, nameLookupURL)]).then(function (values) { if (values.every(Boolean)) { resolve(true); } else { resolve(false); } }); }); } },{"../index":12,"jsontokens":383}],7:[function(require,module,exports){ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _authApp = require('./authApp'); Object.defineProperty(exports, 'isUserSignedIn', { enumerable: true, get: function get() { return _authApp.isUserSignedIn; } }); Object.defineProperty(exports, 'redirectToSignIn', { enumerable: true, get: function get() { return _authApp.redirectToSignIn; } }); Object.defineProperty(exports, 'redirectToSignInWithAuthRequest', { enumerable: true, get: function get() { return _authApp.redirectToSignInWithAuthRequest; } }); Object.defineProperty(exports, 'getAuthResponseToken', { enumerable: true, get: function get() { return _authApp.getAuthResponseToken; } }); Object.defineProperty(exports, 'isSignInPending', { enumerable: true, get: function get() {