blockstack
Version:
The Blockstack Javascript library for authentication, identity, and storage.
253 lines • 12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const queryString = require("query-string");
const jsontokens_1 = require("jsontokens");
const authVerification_1 = require("./authVerification");
const utils_1 = require("../utils");
const fetchUtil_1 = require("../fetchUtil");
const dids_1 = require("../dids");
const errors_1 = require("../errors");
const authMessages_1 = require("./authMessages");
const authConstants_1 = require("./authConstants");
const profileTokens_1 = require("../profiles/profileTokens");
const userSession_1 = require("./userSession");
const config_1 = require("../config");
const logger_1 = require("../logger");
const protocolEchoDetection_1 = require("./protocolEchoDetection");
const protocolLaunch_1 = require("./protocolLaunch");
const keys_1 = require("../keys");
const DEFAULT_PROFILE = {
'@type': 'Person',
'@context': 'http://schema.org'
};
/**
* Check if there is a authentication request that hasn't been handled.
*
* Also checks for a protocol echo reply (which if detected then the page
* will be automatically redirected after this call).
*
* @return {Boolean} `true` if there is a pending sign in, otherwise `false`
*/
function isSignInPending() {
try {
const isProtocolEcho = protocolEchoDetection_1.protocolEchoReplyDetection();
if (isProtocolEcho) {
logger_1.Logger.info('protocolEchoReply detected from isSignInPending call, the page is about to redirect.');
return true;
}
}
catch (error) {
logger_1.Logger.error(`Error checking for protocol echo reply isSignInPending: ${error}`);
}
return !!getAuthResponseToken();
}
exports.isSignInPending = isSignInPending;
/**
* Retrieve the authentication token from the URL query
* @return {String} the authentication token if it exists otherwise `null`
*/
function getAuthResponseToken() {
const search = utils_1.getGlobalObject('location', { throwIfUnavailable: true, usageDesc: 'getAuthResponseToken' }).search;
const queryDict = queryString.parse(search);
return queryDict.authResponse ? queryDict.authResponse : '';
}
exports.getAuthResponseToken = getAuthResponseToken;
/**
* Sign the user out and optionally redirect to given location.
* @param redirectURL
* Location to redirect user to after sign out.
* Only used in environments with `window` available
*/
function signUserOut(redirectURL, caller) {
const userSession = caller || new userSession_1.UserSession();
userSession.store.deleteSessionData();
if (redirectURL) {
utils_1.getGlobalObject('location', { throwIfUnavailable: true, usageDesc: 'signUserOut' }).href = redirectURL;
}
}
exports.signUserOut = signUserOut;
/**
* 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(authRequest, blockstackIDHost = authConstants_1.DEFAULT_BLOCKSTACK_HOST) {
authRequest = authRequest || authMessages_1.makeAuthRequest();
const httpsURI = `${blockstackIDHost}?authRequest=${authRequest}`;
const { navigator, location } = utils_1.getGlobalObjects(['navigator', 'location'], { throwIfUnavailable: true, usageDesc: 'redirectToSignInWithAuthRequest' });
// 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_1.Logger.info('detected mobile OS, sending to https');
location.href = httpsURI;
return;
}
function successCallback() {
logger_1.Logger.info('protocol handler detected');
// The detection function should open the link for us
}
function failCallback() {
logger_1.Logger.warn('protocol handler not detected');
location.href = httpsURI;
}
protocolLaunch_1.launchCustomProtocol(authRequest, successCallback, failCallback);
}
exports.redirectToSignInWithAuthRequest = redirectToSignInWithAuthRequest;
/**
* 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(nameLookupURL = '', authResponseToken = getAuthResponseToken(), transitKey, caller) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
try {
const isProtocolEcho = protocolEchoDetection_1.protocolEchoReplyDetection();
if (isProtocolEcho) {
const msg = 'handlePendingSignIn called while protocolEchoReply was detected, and '
+ 'the page is about to redirect. This function will resolve with an error after '
+ 'several seconds, if the page was not redirected for some reason.';
logger_1.Logger.info(msg);
return new Promise((_resolve, reject) => {
setTimeout(() => {
logger_1.Logger.error('Page should have redirected by now. handlePendingSignIn will now throw.');
reject(msg);
}, 3000);
});
}
}
catch (error) {
logger_1.Logger.error(`Error checking for protocol echo reply handlePendingSignIn: ${error}`);
}
if (!caller) {
caller = new userSession_1.UserSession();
}
const sessionData = caller.store.getSessionData();
if (sessionData.userData) {
throw new errors_1.LoginFailedError('Existing user session found.');
}
if (!transitKey) {
transitKey = caller.store.getSessionData().transitKey;
}
if (!nameLookupURL) {
let coreNode = caller.appConfig && caller.appConfig.coreNode;
if (!coreNode) {
coreNode = config_1.config.network.blockstackAPIUrl;
}
const tokenPayload = jsontokens_1.decodeToken(authResponseToken).payload;
if (typeof tokenPayload === 'string') {
throw new Error('Unexpected token payload type of string');
}
if (utils_1.isLaterVersion(tokenPayload.version, '1.3.0')
&& tokenPayload.blockstackAPIUrl !== null && tokenPayload.blockstackAPIUrl !== undefined) {
// override globally
logger_1.Logger.info(`Overriding ${config_1.config.network.blockstackAPIUrl} `
+ `with ${tokenPayload.blockstackAPIUrl}`);
// TODO: this config is never saved so the user node preference
// is not respected in later sessions..
config_1.config.network.blockstackAPIUrl = tokenPayload.blockstackAPIUrl;
coreNode = tokenPayload.blockstackAPIUrl;
}
nameLookupURL = `${coreNode}${authConstants_1.NAME_LOOKUP_PATH}`;
}
const isValid = yield authVerification_1.verifyAuthResponse(authResponseToken, nameLookupURL);
if (!isValid) {
throw new errors_1.LoginFailedError('Invalid authentication response.');
}
const tokenPayload = jsontokens_1.decodeToken(authResponseToken).payload;
if (typeof tokenPayload === 'string') {
throw new Error('Unexpected token payload type of string');
}
// TODO: real version handling
let appPrivateKey = tokenPayload.private_key;
let coreSessionToken = tokenPayload.core_token;
if (utils_1.isLaterVersion(tokenPayload.version, '1.1.0')) {
if (transitKey !== undefined && transitKey != null) {
if (tokenPayload.private_key !== undefined && tokenPayload.private_key !== null) {
try {
appPrivateKey = yield authMessages_1.decryptPrivateKey(transitKey, tokenPayload.private_key);
}
catch (e) {
logger_1.Logger.warn('Failed decryption of appPrivateKey, will try to use as given');
try {
keys_1.hexStringToECPair(tokenPayload.private_key);
}
catch (ecPairError) {
throw new errors_1.LoginFailedError('Failed decrypting appPrivateKey. Usually means'
+ ' that the transit key has changed during login.');
}
}
}
if (coreSessionToken !== undefined && coreSessionToken !== null) {
try {
coreSessionToken = yield authMessages_1.decryptPrivateKey(transitKey, coreSessionToken);
}
catch (e) {
logger_1.Logger.info('Failed decryption of coreSessionToken, will try to use as given');
}
}
}
else {
throw new errors_1.LoginFailedError('Authenticating with protocol > 1.1.0 requires transit'
+ ' key, and none found.');
}
}
let hubUrl = authConstants_1.BLOCKSTACK_DEFAULT_GAIA_HUB_URL;
let gaiaAssociationToken;
if (utils_1.isLaterVersion(tokenPayload.version, '1.2.0')
&& tokenPayload.hubUrl !== null && tokenPayload.hubUrl !== undefined) {
hubUrl = tokenPayload.hubUrl;
}
if (utils_1.isLaterVersion(tokenPayload.version, '1.3.0')
&& tokenPayload.associationToken !== null && tokenPayload.associationToken !== undefined) {
gaiaAssociationToken = tokenPayload.associationToken;
}
const userData = {
username: tokenPayload.username,
profile: tokenPayload.profile,
email: tokenPayload.email,
decentralizedID: tokenPayload.iss,
identityAddress: dids_1.getAddressFromDID(tokenPayload.iss),
appPrivateKey,
coreSessionToken,
authResponseToken,
hubUrl,
coreNode: tokenPayload.blockstackAPIUrl,
gaiaAssociationToken
};
const profileURL = tokenPayload.profile_url;
if (!userData.profile && profileURL) {
const response = yield fetchUtil_1.fetchPrivate(profileURL);
if (!response.ok) { // return blank profile if we fail to fetch
userData.profile = Object.assign({}, DEFAULT_PROFILE);
}
else {
const responseText = yield response.text();
const wrappedProfile = JSON.parse(responseText);
const profile = profileTokens_1.extractProfile(wrappedProfile[0].token);
userData.profile = profile;
}
}
else {
userData.profile = tokenPayload.profile;
}
sessionData.userData = userData;
caller.store.setSessionData(sessionData);
return userData;
});
}
exports.handlePendingSignIn = handlePendingSignIn;
//# sourceMappingURL=authApp.js.map