From 3a1dfbad852e67a8aeeafe3cad635c4a066fe4fa Mon Sep 17 00:00:00 2001 From: Yukan Date: Mon, 24 Aug 2020 16:31:47 -0400 Subject: [PATCH] fix: more circular dependencies removed from auth package --- packages/auth/src/auth.ts | 401 +++++++++++++++---------------- packages/auth/src/constants.ts | 8 + packages/auth/src/index.ts | 3 - packages/auth/src/messages.ts | 9 +- packages/auth/src/sessionData.ts | 2 +- packages/auth/src/userData.ts | 39 +++ packages/auth/src/userSession.ts | 181 ++++++++++++-- 7 files changed, 405 insertions(+), 238 deletions(-) create mode 100644 packages/auth/src/userData.ts diff --git a/packages/auth/src/auth.ts b/packages/auth/src/auth.ts index 06febce7d..72c3b295b 100644 --- a/packages/auth/src/auth.ts +++ b/packages/auth/src/auth.ts @@ -15,225 +15,220 @@ import { NAME_LOOKUP_PATH } from './constants' import { extractProfile } from '@stacks/profile' -import { UserSession } from './userSession' +// import { UserSession } from './userSession' import { hexStringToECPair } from '@stacks/encryption' import { StacksMainnet } from '@stacks/network' -const DEFAULT_PROFILE = { - '@type': 'Person', - '@context': 'http://schema.org' -} +// /** +// * Returned from the [[UserSession.loadUserData]] function. +// */ +// export interface UserData { +// // public: the blockstack ID (for example: stackerson.id or alice.blockstack.id) +// username: string; +// // public: the email address for the user. only available if the `email` +// // scope is requested, and if the user has entered a valid email into +// // their profile. +// // +// // **Note**: Blockstack does not require email validation +// // for users for privacy reasons and blah blah (something like this, idk) +// email?: string; +// // probably public: (a quick description of what this is, and a link to the +// // DID foundation and/or the blockstack docs related to DID, idk) +// decentralizedID: string; +// // probably private: looks like it happens to be the btc address but idk +// // the value of establishing this as a supported field +// identityAddress: string; +// // probably public: this is an advanced feature, I think many app devs +// // using our more advanced encryption functions (as opposed to putFile/getFile), +// // are probably using this. seems useful to explain. +// appPrivateKey: string; +// // maybe public: possibly useful for advanced devs / webapps. I see an opportunity +// // to make a small plug about "user owned data" here, idk. +// hubUrl: string; +// coreNode: string; +// // maybe private: this would be an advanced field for app devs to use. +// authResponseToken: string; +// // private: does not get sent to webapp at all. +// coreSessionToken?: string; +// // private: does not get sent to webapp at all. +// gaiaAssociationToken?: string; +// // public: this is the proper `Person` schema json for the user. +// // This is the data that gets used when the `new blockstack.Person(profile)` class is used. +// profile: any; +// // private: does not get sent to webapp at all. +// gaiaHubConfig?: any; +// } -/** - * Returned from the [[UserSession.loadUserData]] function. - */ -export interface UserData { - // public: the blockstack ID (for example: stackerson.id or alice.blockstack.id) - username: string; - // public: the email address for the user. only available if the `email` - // scope is requested, and if the user has entered a valid email into - // their profile. - // - // **Note**: Blockstack does not require email validation - // for users for privacy reasons and blah blah (something like this, idk) - email?: string; - // probably public: (a quick description of what this is, and a link to the - // DID foundation and/or the blockstack docs related to DID, idk) - decentralizedID: string; - // probably private: looks like it happens to be the btc address but idk - // the value of establishing this as a supported field - identityAddress: string; - // probably public: this is an advanced feature, I think many app devs - // using our more advanced encryption functions (as opposed to putFile/getFile), - // are probably using this. seems useful to explain. - appPrivateKey: string; - // maybe public: possibly useful for advanced devs / webapps. I see an opportunity - // to make a small plug about "user owned data" here, idk. - hubUrl: string; - coreNode: string; - // maybe private: this would be an advanced field for app devs to use. - authResponseToken: string; - // private: does not get sent to webapp at all. - coreSessionToken?: string; - // private: does not get sent to webapp at all. - gaiaAssociationToken?: string; - // public: this is the proper `Person` schema json for the user. - // This is the data that gets used when the `new blockstack.Person(profile)` class is used. - profile: any; - // private: does not get sent to webapp at all. - gaiaHubConfig?: any; -} +// /** +// * Retrieve the authentication token from the URL query +// * @return {String} the authentication token if it exists otherwise `null` +// */ +// export function getAuthResponseToken(): string { +// const search = getGlobalObject( +// 'location', +// { throwIfUnavailable: true, usageDesc: 'getAuthResponseToken' } +// ).search +// const queryDict = queryString.parse(search) +// return queryDict.authResponse ? queryDict.authResponse : '' +// } -/** - * Retrieve the authentication token from the URL query - * @return {String} the authentication token if it exists otherwise `null` - */ -export function getAuthResponseToken(): string { - const search = getGlobalObject( - 'location', - { throwIfUnavailable: true, usageDesc: 'getAuthResponseToken' } - ).search - const queryDict = queryString.parse(search) - return queryDict.authResponse ? queryDict.authResponse : '' -} +// /** +// * 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 +// */ +// export function signUserOut(redirectURL?: string, caller?: UserSession) { +// const userSession = caller || new UserSession() +// userSession.store.deleteSessionData() +// if (redirectURL) { +// getGlobalObject( +// 'location', +// { throwIfUnavailable: true, usageDesc: 'signUserOut' } +// ).href = redirectURL +// } +// } -/** - * 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 - */ -export function signUserOut(redirectURL?: string, caller?: UserSession) { - const userSession = caller || new UserSession() - userSession.store.deleteSessionData() - if (redirectURL) { - getGlobalObject( - 'location', - { throwIfUnavailable: true, usageDesc: 'signUserOut' } - ).href = redirectURL - } -} +// /** +// * 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. +// */ +// export async function handlePendingSignIn( +// nameLookupURL: string = '', +// authResponseToken: string = getAuthResponseToken(), +// transitKey?: string, +// caller?: UserSession +// ): Promise { +// if (!caller) { +// caller = new UserSession() +// } -/** - * 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. - */ -export async function handlePendingSignIn( - nameLookupURL: string = '', - authResponseToken: string = getAuthResponseToken(), - transitKey?: string, - caller?: UserSession -): Promise { - if (!caller) { - caller = new UserSession() - } +// const sessionData = caller.store.getSessionData() - const sessionData = caller.store.getSessionData() +// if (sessionData.userData) { +// throw new LoginFailedError('Existing user session found.') +// } - if (sessionData.userData) { - throw new LoginFailedError('Existing user session found.') - } +// if (!transitKey) { +// transitKey = caller.store.getSessionData().transitKey +// } +// if (!nameLookupURL) { +// let coreNode = caller.appConfig && caller.appConfig.coreNode +// if (!coreNode) { +// let network = new StacksMainnet() +// coreNode = network.coreApiUrl +// } - if (!transitKey) { - transitKey = caller.store.getSessionData().transitKey - } - if (!nameLookupURL) { - let coreNode = caller.appConfig && caller.appConfig.coreNode - if (!coreNode) { - let network = new StacksMainnet() - coreNode = network.coreApiUrl - } - - const tokenPayload = decodeToken(authResponseToken).payload - if (typeof tokenPayload === 'string') { - throw new Error('Unexpected token payload type of string') - } +// const tokenPayload = decodeToken(authResponseToken).payload +// if (typeof tokenPayload === 'string') { +// throw new Error('Unexpected token payload type of string') +// } - // Section below is removed since the config was never persisted and therefore useless +// // Section below is removed since the config was never persisted and therefore useless - // if (isLaterVersion(tokenPayload.version as string, '1.3.0') - // && tokenPayload.blockstackAPIUrl !== null && tokenPayload.blockstackAPIUrl !== undefined) { - // // override globally - // Logger.info(`Overriding ${config.network.blockstackAPIUrl} ` - // + `with ${tokenPayload.blockstackAPIUrl}`) - // // TODO: this config is never saved so the user node preference - // // is not respected in later sessions.. - // config.network.blockstackAPIUrl = tokenPayload.blockstackAPIUrl as string - // coreNode = tokenPayload.blockstackAPIUrl as string - // } +// // if (isLaterVersion(tokenPayload.version as string, '1.3.0') +// // && tokenPayload.blockstackAPIUrl !== null && tokenPayload.blockstackAPIUrl !== undefined) { +// // // override globally +// // Logger.info(`Overriding ${config.network.blockstackAPIUrl} ` +// // + `with ${tokenPayload.blockstackAPIUrl}`) +// // // TODO: this config is never saved so the user node preference +// // // is not respected in later sessions.. +// // config.network.blockstackAPIUrl = tokenPayload.blockstackAPIUrl as string +// // coreNode = tokenPayload.blockstackAPIUrl as string +// // } - nameLookupURL = `${coreNode}${NAME_LOOKUP_PATH}` - } +// nameLookupURL = `${coreNode}${NAME_LOOKUP_PATH}` +// } - const isValid = await verifyAuthResponse(authResponseToken, nameLookupURL) - if (!isValid) { - throw new LoginFailedError('Invalid authentication response.') - } - const tokenPayload = decodeToken(authResponseToken).payload - if (typeof tokenPayload === 'string') { - throw new Error('Unexpected token payload type of string') - } +// const isValid = await verifyAuthResponse(authResponseToken, nameLookupURL) +// if (!isValid) { +// throw new LoginFailedError('Invalid authentication response.') +// } +// const tokenPayload = 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 as string - let coreSessionToken = tokenPayload.core_token as string - if (isLaterVersion(tokenPayload.version as string, '1.1.0')) { - if (transitKey !== undefined && transitKey != null) { - if (tokenPayload.private_key !== undefined && tokenPayload.private_key !== null) { - try { - appPrivateKey = await decryptPrivateKey(transitKey, tokenPayload.private_key as string) - } catch (e) { - Logger.warn('Failed decryption of appPrivateKey, will try to use as given') - try { - hexStringToECPair(tokenPayload.private_key as string) - } catch (ecPairError) { - throw new LoginFailedError('Failed decrypting appPrivateKey. Usually means' - + ' that the transit key has changed during login.') - } - } - } - if (coreSessionToken !== undefined && coreSessionToken !== null) { - try { - coreSessionToken = await decryptPrivateKey(transitKey, coreSessionToken) - } catch (e) { - Logger.info('Failed decryption of coreSessionToken, will try to use as given') - } - } - } else { - throw new LoginFailedError('Authenticating with protocol > 1.1.0 requires transit' - + ' key, and none found.') - } - } - let hubUrl = BLOCKSTACK_DEFAULT_GAIA_HUB_URL - let gaiaAssociationToken: string - if (isLaterVersion(tokenPayload.version as string, '1.2.0') - && tokenPayload.hubUrl !== null && tokenPayload.hubUrl !== undefined) { - hubUrl = tokenPayload.hubUrl as string - } - if (isLaterVersion(tokenPayload.version as string, '1.3.0') - && tokenPayload.associationToken !== null && tokenPayload.associationToken !== undefined) { - gaiaAssociationToken = tokenPayload.associationToken as string - } +// // TODO: real version handling +// let appPrivateKey = tokenPayload.private_key as string +// let coreSessionToken = tokenPayload.core_token as string +// if (isLaterVersion(tokenPayload.version as string, '1.1.0')) { +// if (transitKey !== undefined && transitKey != null) { +// if (tokenPayload.private_key !== undefined && tokenPayload.private_key !== null) { +// try { +// appPrivateKey = await decryptPrivateKey(transitKey, tokenPayload.private_key as string) +// } catch (e) { +// Logger.warn('Failed decryption of appPrivateKey, will try to use as given') +// try { +// hexStringToECPair(tokenPayload.private_key as string) +// } catch (ecPairError) { +// throw new LoginFailedError('Failed decrypting appPrivateKey. Usually means' +// + ' that the transit key has changed during login.') +// } +// } +// } +// if (coreSessionToken !== undefined && coreSessionToken !== null) { +// try { +// coreSessionToken = await decryptPrivateKey(transitKey, coreSessionToken) +// } catch (e) { +// Logger.info('Failed decryption of coreSessionToken, will try to use as given') +// } +// } +// } else { +// throw new LoginFailedError('Authenticating with protocol > 1.1.0 requires transit' +// + ' key, and none found.') +// } +// } +// let hubUrl = BLOCKSTACK_DEFAULT_GAIA_HUB_URL +// let gaiaAssociationToken: string +// if (isLaterVersion(tokenPayload.version as string, '1.2.0') +// && tokenPayload.hubUrl !== null && tokenPayload.hubUrl !== undefined) { +// hubUrl = tokenPayload.hubUrl as string +// } +// if (isLaterVersion(tokenPayload.version as string, '1.3.0') +// && tokenPayload.associationToken !== null && tokenPayload.associationToken !== undefined) { +// gaiaAssociationToken = tokenPayload.associationToken as string +// } - const userData: UserData = { - username: tokenPayload.username as string, - profile: tokenPayload.profile, - email: tokenPayload.email as string, - decentralizedID: tokenPayload.iss, - identityAddress: getAddressFromDID(tokenPayload.iss), - appPrivateKey, - coreSessionToken, - authResponseToken, - hubUrl, - coreNode: tokenPayload.blockstackAPIUrl as string, - gaiaAssociationToken - } - const profileURL = tokenPayload.profile_url as string - if (!userData.profile && profileURL) { - const response = await fetchPrivate(profileURL) - if (!response.ok) { // return blank profile if we fail to fetch - userData.profile = Object.assign({}, DEFAULT_PROFILE) - } else { - const responseText = await response.text() - const wrappedProfile = JSON.parse(responseText) - const profile = extractProfile(wrappedProfile[0].token) - userData.profile = profile - } - } else { - userData.profile = tokenPayload.profile - } +// const userData: UserData = { +// username: tokenPayload.username as string, +// profile: tokenPayload.profile, +// email: tokenPayload.email as string, +// decentralizedID: tokenPayload.iss, +// identityAddress: getAddressFromDID(tokenPayload.iss), +// appPrivateKey, +// coreSessionToken, +// authResponseToken, +// hubUrl, +// coreNode: tokenPayload.blockstackAPIUrl as string, +// gaiaAssociationToken +// } +// const profileURL = tokenPayload.profile_url as string +// if (!userData.profile && profileURL) { +// const response = await fetchPrivate(profileURL) +// if (!response.ok) { // return blank profile if we fail to fetch +// userData.profile = Object.assign({}, DEFAULT_PROFILE) +// } else { +// const responseText = await response.text() +// const wrappedProfile = JSON.parse(responseText) +// const profile = extractProfile(wrappedProfile[0].token) +// userData.profile = profile +// } +// } else { +// userData.profile = tokenPayload.profile +// } - sessionData.userData = userData - caller.store.setSessionData(sessionData) +// sessionData.userData = userData +// caller.store.setSessionData(sessionData) - return userData -} +// return userData +// } diff --git a/packages/auth/src/constants.ts b/packages/auth/src/constants.ts index 925b502aa..680109261 100644 --- a/packages/auth/src/constants.ts +++ b/packages/auth/src/constants.ts @@ -12,6 +12,14 @@ export const BLOCKSTACK_STORAGE_LABEL = 'blockstack' */ export const DEFAULT_BLOCKSTACK_HOST = 'https://browser.blockstack.org/auth' +/** +* Default user profile object +*/ +export const DEFAULT_PROFILE = { + '@type': 'Person', + '@context': 'http://schema.org' +} + /** * Non-exhaustive list of common permission scopes. */ diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index 0ccfb94c9..dc48e6ac5 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -13,9 +13,6 @@ export { doPublicKeysMatchIssuer, doSignaturesMatchPublicKeys, isManifestUriValid, isRedirectUriValid, verifyAuthRequestAndLoadManifest } from './verification' -export { - handlePendingSignIn, signUserOut -} from './auth' export { UserSession } from './userSession' diff --git a/packages/auth/src/messages.ts b/packages/auth/src/messages.ts index d6a0f60bc..6e802b24d 100644 --- a/packages/auth/src/messages.ts +++ b/packages/auth/src/messages.ts @@ -6,8 +6,6 @@ import { makeUUID4, nextMonth, getGlobalObject, Logger } from '@stacks/common' import { makeDIDFromAddress } from './dids' import { encryptECIES, decryptECIES, makeECPrivateKey, publicKeyToAddress } from '@stacks/encryption' import { DEFAULT_SCOPE, AuthScope } from './constants' -import { UserSession } from './userSession' - const VERSION = '1.3.1' @@ -29,7 +27,6 @@ export function generateTransitKey() { return transitKey } - /** * Generates an authentication request that can be sent to the Blockstack * browser for the user to approve sign in. This authentication request can @@ -52,7 +49,7 @@ export function generateTransitKey() { * @return {String} the authentication request */ export function makeAuthRequest( - transitPrivateKey?: string, + transitPrivateKey: string, redirectURI?: string, manifestURI?: string, scopes: Array = DEFAULT_SCOPE.slice(), @@ -60,10 +57,6 @@ export function makeAuthRequest( expiresAt: number = nextMonth().getTime(), extraParams: any = {} ): string { - if (!transitPrivateKey) { - transitPrivateKey = new UserSession().generateAndStoreTransitKey() - } - const getWindowOrigin = (paramName: string) => { const location = getGlobalObject('location', { throwIfUnavailable: true, diff --git a/packages/auth/src/sessionData.ts b/packages/auth/src/sessionData.ts index 7f1658652..be49d4312 100644 --- a/packages/auth/src/sessionData.ts +++ b/packages/auth/src/sessionData.ts @@ -1,5 +1,5 @@ import { InvalidStateError } from '@stacks/common' -import { UserData } from './auth' +import { UserData } from './userData' const SESSION_VERSION = '1.0.0' diff --git a/packages/auth/src/userData.ts b/packages/auth/src/userData.ts new file mode 100644 index 000000000..e6543cbdb --- /dev/null +++ b/packages/auth/src/userData.ts @@ -0,0 +1,39 @@ +/** + * Returned from the [[UserSession.loadUserData]] function. + */ +export interface UserData { + // public: the blockstack ID (for example: stackerson.id or alice.blockstack.id) + username: string; + // public: the email address for the user. only available if the `email` + // scope is requested, and if the user has entered a valid email into + // their profile. + // + // **Note**: Blockstack does not require email validation + // for users for privacy reasons and blah blah (something like this, idk) + email?: string; + // probably public: (a quick description of what this is, and a link to the + // DID foundation and/or the blockstack docs related to DID, idk) + decentralizedID: string; + // probably private: looks like it happens to be the btc address but idk + // the value of establishing this as a supported field + identityAddress: string; + // probably public: this is an advanced feature, I think many app devs + // using our more advanced encryption functions (as opposed to putFile/getFile), + // are probably using this. seems useful to explain. + appPrivateKey: string; + // maybe public: possibly useful for advanced devs / webapps. I see an opportunity + // to make a small plug about "user owned data" here, idk. + hubUrl: string; + coreNode: string; + // maybe private: this would be an advanced field for app devs to use. + authResponseToken: string; + // private: does not get sent to webapp at all. + coreSessionToken?: string; + // private: does not get sent to webapp at all. + gaiaAssociationToken?: string; + // public: this is the proper `Person` schema json for the user. + // This is the data that gets used when the `new blockstack.Person(profile)` class is used. + profile: any; + // private: does not get sent to webapp at all. + gaiaHubConfig?: any; +} diff --git a/packages/auth/src/userSession.ts b/packages/auth/src/userSession.ts index 0d99f122d..608f55c9e 100644 --- a/packages/auth/src/userSession.ts +++ b/packages/auth/src/userSession.ts @@ -5,19 +5,31 @@ import { SessionDataStore, InstanceDataStore } from './sessionStore' - +import { decodeToken } from 'jsontokens' +import { verifyAuthResponse } from './verification' import * as authMessages from './messages' - +import { hexStringToECPair } from '@stacks/encryption' +import { getAddressFromDID } from './dids' import { nextHour, MissingParameterError, InvalidStateError, - Logger + Logger, + getGlobalObject, + LoginFailedError, + isLaterVersion, + fetchPrivate, + BLOCKSTACK_DEFAULT_GAIA_HUB_URL } from '@stacks/common' -import { AuthScope } from './constants' -import { handlePendingSignIn, signUserOut, getAuthResponseToken } from './auth' +import { extractProfile } from '@stacks/profile' +import { AuthScope, DEFAULT_PROFILE } from './constants' import { encryptContent, decryptContent, EncryptContentOptions } from '@stacks/encryption'; - +import * as queryString from 'query-string' +import { UserData } from './userData' +import { StacksMainnet } from '@stacks/network' +import { + NAME_LOOKUP_PATH +} from './constants' /** * @@ -138,14 +150,19 @@ export class UserSession { } /** - * Retrieve the authentication token from the URL query. - * - * @returns {String} the authentication token if it exists otherwise `null` + * Retrieve the authentication token from the URL query + * @return {String} the authentication token if it exists otherwise `null` */ getAuthResponseToken(): string { - return getAuthResponseToken() + const search = getGlobalObject( + 'location', + { throwIfUnavailable: true, usageDesc: 'getAuthResponseToken' } + ).search + const queryDict = queryString.parse(search) + return queryDict.authResponse ? queryDict.authResponse : '' } + /** * Check if a user is currently signed in. * @@ -163,9 +180,122 @@ export class UserSession { * @returns {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. */ - handlePendingSignIn(authResponseToken: string = this.getAuthResponseToken()) { - const transitKey = this.store.getSessionData().transitKey - return handlePendingSignIn(undefined, authResponseToken, transitKey, this) + async handlePendingSignIn(authResponseToken: string = this.getAuthResponseToken()): Promise { + const sessionData = this.store.getSessionData() + + if (sessionData.userData) { + throw new LoginFailedError('Existing user session found.') + } + + let transitKey = this.store.getSessionData().transitKey + + + let nameLookupURL; + let coreNode = this.appConfig && this.appConfig.coreNode + if (!coreNode) { + let network = new StacksMainnet() + coreNode = network.coreApiUrl + } + + let tokenPayload = decodeToken(authResponseToken).payload + + if (typeof tokenPayload === 'string') { + throw new Error('Unexpected token payload type of string') + } + + // Section below is removed since the config was never persisted and therefore useless + + // if (isLaterVersion(tokenPayload.version as string, '1.3.0') + // && tokenPayload.blockstackAPIUrl !== null && tokenPayload.blockstackAPIUrl !== undefined) { + // // override globally + // Logger.info(`Overriding ${config.network.blockstackAPIUrl} ` + // + `with ${tokenPayload.blockstackAPIUrl}`) + // // TODO: this config is never saved so the user node preference + // // is not respected in later sessions.. + // config.network.blockstackAPIUrl = tokenPayload.blockstackAPIUrl as string + // coreNode = tokenPayload.blockstackAPIUrl as string + // } + + nameLookupURL = `${coreNode}${NAME_LOOKUP_PATH}` + + const isValid = await verifyAuthResponse(authResponseToken, nameLookupURL) + if (!isValid) { + throw new LoginFailedError('Invalid authentication response.') + } + + // TODO: real version handling + let appPrivateKey = tokenPayload.private_key as string + let coreSessionToken = tokenPayload.core_token as string + if (isLaterVersion(tokenPayload.version as string, '1.1.0')) { + if (transitKey !== undefined && transitKey != null) { + if (tokenPayload.private_key !== undefined && tokenPayload.private_key !== null) { + try { + appPrivateKey = await authMessages.decryptPrivateKey(transitKey, tokenPayload.private_key as string) + } catch (e) { + Logger.warn('Failed decryption of appPrivateKey, will try to use as given') + try { + hexStringToECPair(tokenPayload.private_key as string) + } catch (ecPairError) { + throw new LoginFailedError('Failed decrypting appPrivateKey. Usually means' + + ' that the transit key has changed during login.') + } + } + } + if (coreSessionToken !== undefined && coreSessionToken !== null) { + try { + coreSessionToken = await authMessages.decryptPrivateKey(transitKey, coreSessionToken) + } catch (e) { + Logger.info('Failed decryption of coreSessionToken, will try to use as given') + } + } + } else { + throw new LoginFailedError('Authenticating with protocol > 1.1.0 requires transit' + + ' key, and none found.') + } + } + let hubUrl = BLOCKSTACK_DEFAULT_GAIA_HUB_URL + let gaiaAssociationToken: string + if (isLaterVersion(tokenPayload.version as string, '1.2.0') + && tokenPayload.hubUrl !== null && tokenPayload.hubUrl !== undefined) { + hubUrl = tokenPayload.hubUrl as string + } + if (isLaterVersion(tokenPayload.version as string, '1.3.0') + && tokenPayload.associationToken !== null && tokenPayload.associationToken !== undefined) { + gaiaAssociationToken = tokenPayload.associationToken as string + } + + const userData: UserData = { + username: tokenPayload.username as string, + profile: tokenPayload.profile, + email: tokenPayload.email as string, + decentralizedID: tokenPayload.iss, + identityAddress: getAddressFromDID(tokenPayload.iss), + appPrivateKey, + coreSessionToken, + authResponseToken, + hubUrl, + coreNode: tokenPayload.blockstackAPIUrl as string, + gaiaAssociationToken + } + const profileURL = tokenPayload.profile_url as string + if (!userData.profile && profileURL) { + const response = await fetchPrivate(profileURL) + if (!response.ok) { // return blank profile if we fail to fetch + userData.profile = Object.assign({}, DEFAULT_PROFILE) + } else { + const responseText = await response.text() + const wrappedProfile = JSON.parse(responseText) + const profile = extractProfile(wrappedProfile[0].token) + userData.profile = profile + } + } else { + userData.profile = tokenPayload.profile + } + + sessionData.userData = userData + this.store.setSessionData(sessionData) + + return userData } /** @@ -181,16 +311,6 @@ export class UserSession { return userData } - - /** - * 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 - */ - signUserOut(redirectURL?: string) { - signUserOut(redirectURL, this) - } - /** * Encrypts the data provided with the app public key. * @param {String|Buffer} content the data to encrypt @@ -221,4 +341,19 @@ export class UserSession { return decryptContent(content, options) } + /** + * 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 + */ + signUserOut(redirectURL?: string, caller?: UserSession) { + this.store.deleteSessionData() + if (redirectURL) { + getGlobalObject( + 'location', + { throwIfUnavailable: true, usageDesc: 'signUserOut' } + ).href = redirectURL + } + } }