<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import { ShellParent, AppHomeWrapper } from '@blockstack/ui'
import { Initial, LegacyGaia } from './views'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { AuthActions } from './store/auth'
import { decodeToken } from 'jsontokens'
import {
  makeAuthResponse,
  getAuthRequestFromURL,
  redirectUserToApp,
  getAppBucketUrl,
  isLaterVersion
} from 'blockstack'
import { AppsNode } from '@utils/account-utils'
import {
  fetchProfileLocations,
  getDefaultProfileUrl
} from '@utils/profile-utils'
import { getTokenFileUrlFromZoneFile } from '@utils/zone-utils'
import { HDNode } from 'bitcoinjs-lib'
import log4js from 'log4js'
import { uploadProfile } from '../account/utils'
import { signProfileForUpload } from '@utils'
import { validateScopes, appRequestSupportsDirectHub } from './utils'
import {
  selectApi,
  selectCoreHost,
  selectCorePort,
  selectCoreAPIPassword
} from '@common/store/selectors/settings'
import {
  selectAppManifest,
  selectAppManifestLoading,
  selectAppManifestLoadingError,
  selectCoreSessionTokens
} from '@common/store/selectors/auth'
import {
  selectLocalIdentities,
  selectDefaultIdentity
} from '@common/store/selectors/profiles'

import {
  selectIdentityKeypairs,
  selectEmail,
  selectIdentityAddresses,
  selectPublicKeychain
} from '@common/store/selectors/account'
import { formatAppManifest } from '@common'

const views = [Initial, LegacyGaia]

const logger = log4js.getLogger('auth/components/AuthModal.js')

const VIEWS = {
  AUTH: 0,
  LEGACY_GAIA: 1
}
function mapStateToProps(state) {
  return {
    localIdentities: selectLocalIdentities(state),
    defaultIdentity: selectDefaultIdentity(state),
    identityKeypairs: selectIdentityKeypairs(state),
    appManifest: selectAppManifest(state),
    appManifestLoading: selectAppManifestLoading(state),
    appManifestLoadingError: selectAppManifestLoadingError(state),
    coreSessionTokens: selectCoreSessionTokens(state),
    coreHost: selectCoreHost(state),
    corePort: selectCorePort(state),
    coreAPIPassword: selectCoreAPIPassword(state),
    api: selectApi(state),
    email: selectEmail(state),
    addresses: selectIdentityAddresses(state),
    publicKeychain: selectPublicKeychain(state)
  }
}

function mapDispatchToProps(dispatch) {
  const actions = Object.assign({}, AuthActions)
  return bindActionCreators(actions, dispatch)
}

class AuthPage extends React.Component {
  static contextTypes = {
    router: PropTypes.object
  }
  static propTypes = {
    defaultIdentity: PropTypes.number.isRequired,
    localIdentities: PropTypes.array.isRequired,
    verifyAuthRequestAndLoadManifest: PropTypes.func.isRequired,
    clearSessionToken: PropTypes.func.isRequired,
    getCoreSessionToken: PropTypes.func.isRequired,
    coreAPIPassword: PropTypes.string.isRequired,
    coreSessionTokens: PropTypes.object.isRequired,
    loginToApp: PropTypes.func.isRequired,
    api: PropTypes.object.isRequired,
    identityKeypairs: PropTypes.array.isRequired,
    coreHost: PropTypes.string.isRequired,
    corePort: PropTypes.number.isRequired,
    appManifest: PropTypes.object,
    appManifestLoading: PropTypes.bool,
    email: PropTypes.string,
    noCoreSessionToken: PropTypes.func.isRequired,
    addresses: PropTypes.array.isRequired,
    publicKeychain: PropTypes.string.isRequired
  }

  constructor(props) {
    super(props)

    this.state = {
      currentIdentityIndex: this.props.defaultIdentity,
      authRequest: null,
      appManifest: null,
      coreSessionToken: null,
      decodedToken: null,
      storageConnected: this.props.api.storageConnected,
      processing: false,
      invalidScopes: false,
      sendEmail: false,
      blockchainId: null,
      noCoreStorage: false,
      responseSent: false,
      scopes: {
        email: false,
        publishData: false
      },
      view: VIEWS.AUTH
    }

    this.login = this.login.bind(this)
    this.closeModal = this.closeModal.bind(this)
  }

  componentWillMount() {
    const authRequest = getAuthRequestFromURL()
    const decodedToken = decodeToken(authRequest)
    const { scopes } = decodedToken.payload

    this.setState({
      authRequest,
      decodedToken,
      scopes: {
        ...this.state.scopes,
        email: scopes.includes('email'),
        publishData: scopes.includes('publish_data')
      }
    })

    this.props.verifyAuthRequestAndLoadManifest(authRequest)
  }

  componentWillReceiveProps(nextProps) {
    if (!this.state.responseSent) {
      const storageConnected = this.props.api.storageConnected
      this.setState({
        storageConnected
      })

      const appDomain = this.state.decodedToken.payload.domain_name
      const localIdentities = nextProps.localIdentities
      const identityKeypairs = nextProps.identityKeypairs
      if (!appDomain || !nextProps.coreSessionTokens[appDomain]) {
        if (this.state.noCoreStorage) {
          logger.debug(
            'componentWillReceiveProps: no core session token expected'
          )
        } else {
          logger.debug(
            'componentWillReceiveProps: no app domain or no core session token'
          )
          return
        }
      }

      logger.trace('componentWillReceiveProps')

      const coreSessionToken = nextProps.coreSessionTokens[appDomain]
      let decodedCoreSessionToken = null
      if (!this.state.noCoreStorage) {
        logger.debug('componentWillReceiveProps: received coreSessionToken')
        decodedCoreSessionToken = decodeToken(coreSessionToken)
      } else {
        logger.debug('componentWillReceiveProps: received no coreSessionToken')
      }

      const identityIndex = this.state.currentIdentityIndex

      const hasUsername = this.state.hasUsername
      if (hasUsername) {
        logger.debug(`login(): id index ${identityIndex} has no username`)
      }

      // Get keypair corresponding to the current user identity
      const profileSigningKeypair = identityKeypairs[identityIndex]
      const identity = localIdentities[identityIndex]

      let blockchainId = null
      if (decodedCoreSessionToken) {
        blockchainId = decodedCoreSessionToken.payload.blockchain_id
      } else {
        blockchainId = this.state.blockchainId
      }

      const profile = identity.profile
      const privateKey = profileSigningKeypair.key
      const appsNodeKey = profileSigningKeypair.appsNodeKey
      const salt = profileSigningKeypair.salt
      const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt)
      const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey()

      let profileUrlPromise

      if (identity.zoneFile && identity.zoneFile.length > 0) {
        const profileUrlFromZonefile = getTokenFileUrlFromZoneFile(
          identity.zoneFile
        )
        if (
          profileUrlFromZonefile !== null &&
          profileUrlFromZonefile !== undefined
        ) {
          profileUrlPromise = Promise.resolve(profileUrlFromZonefile)
        }
      }

      const gaiaBucketAddress = nextProps.identityKeypairs[0].address
      const identityAddress = nextProps.identityKeypairs[identityIndex].address
      const gaiaUrlBase = 'https://gaia.blockstack.org/hub'

      if (!profileUrlPromise) {
        profileUrlPromise = fetchProfileLocations(
          gaiaUrlBase,
          identityAddress,
          gaiaBucketAddress,
          identityIndex
        ).then(fetchProfileResp => {
          if (fetchProfileResp && fetchProfileResp.profileUrl) {
            return fetchProfileResp.profileUrl
          } else {
            return getDefaultProfileUrl(gaiaUrlBase, identityAddress)
          }
        })
      }

      profileUrlPromise.then(profileUrl => {
        // Add app storage bucket URL to profile if publish_data scope is requested
        if (this.state.scopes.publishData) {
          let apps = {}
          if (profile.hasOwnProperty('apps')) {
            apps = profile.apps
          }

          if (storageConnected) {
            getAppBucketUrl('https://hub.blockstack.org', appPrivateKey)
              .then(appBucketUrl => {
                logger.debug(
                  `componentWillReceiveProps: appBucketUrl ${appBucketUrl}`
                )
                apps[appDomain] = appBucketUrl
                logger.debug(
                  `componentWillReceiveProps: new apps array ${JSON.stringify(
                    apps
                  )}`
                )
                profile.apps = apps
                const signedProfileTokenData = signProfileForUpload(
                  profile,
                  nextProps.identityKeypairs[identityIndex]
                )
                logger.debug(
                  'componentWillReceiveProps: uploading updated profile with new apps array'
                )
                return uploadProfile(
                  this.props.api,
                  identity,
                  nextProps.identityKeypairs[identityIndex],
                  signedProfileTokenData
                )
              })
              .then(() => {
                this.completeAuthResponse(
                  privateKey,
                  blockchainId,
                  coreSessionToken,
                  appPrivateKey,
                  profile,
                  profileUrl
                )
              })
              .catch(err => {
                logger.error(
                  'componentWillReceiveProps: add app index profile not uploaded',
                  err
                )
              })
          } else {
            logger.debug(
              'componentWillReceiveProps: storage is not connected. Doing nothing.'
            )
          }
        } else {
          this.completeAuthResponse(
            privateKey,
            blockchainId,
            coreSessionToken,
            appPrivateKey,
            profile,
            profileUrl
          )
        }
      })
    } else {
      logger.error(
        'componentWillReceiveProps: response already sent - doing nothing'
      )
    }
  }

  completeAuthResponse = (
    privateKey,
    blockchainId,
    coreSessionToken,
    appPrivateKey,
    profile,
    profileUrl
  ) => {
    const appDomain = this.state.decodedToken.payload.domain_name
    const email = this.props.email
    const sendEmail = this.state.sendEmail

    logger.debug(`profileUrl: ${profileUrl}`)
    logger.debug(`email: ${email}`)

    const metadata = {
      email: sendEmail ? email : null,
      profileUrl
    }

    const doNoIncludeProfile = this.state.decodedToken.payload
      .do_not_include_profile

    const profileResponseData = doNoIncludeProfile ? null : profile

    let transitPublicKey = undefined
    let hubUrl = undefined

    let requestVersion = '0'
    if (this.state.decodedToken.payload.hasOwnProperty('version')) {
      requestVersion = this.state.decodedToken.payload.version
    }

    if (
      isLaterVersion(requestVersion, '1.1.0') &&
      this.state.decodedToken.payload.public_keys.length > 0
    ) {
      transitPublicKey = this.state.decodedToken.payload.public_keys[0]
    }
    if (appRequestSupportsDirectHub(this.state.decodedToken.payload)) {
      hubUrl = this.props.api.gaiaHubConfig.server
    }

    const authResponse = makeAuthResponse(
      privateKey,
      profileResponseData,
      blockchainId,
      metadata,
      coreSessionToken,
      appPrivateKey,
      undefined,
      transitPublicKey,
      hubUrl
    )

    this.props.clearSessionToken(appDomain)

    logger.trace(
      `login(): id index ${this.state.currentIdentityIndex} is logging in`
    )
    this.setState({ responseSent: true })
    redirectUserToApp(this.state.authRequest, authResponse)
  }

  closeModal() {
    this.context.router.push('/')
  }

  login = (identityIndex = this.state.currentIdentityIndex) => {<commit_msg>authenticate with the users configured gaia hub<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import { ShellParent, AppHomeWrapper } from '@blockstack/ui'
import { Initial, LegacyGaia } from './views'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { AuthActions } from './store/auth'
import { decodeToken } from 'jsontokens'
import {
  makeAuthResponse,
  getAuthRequestFromURL,
  redirectUserToApp,
  getAppBucketUrl,
  isLaterVersion
} from 'blockstack'
import { AppsNode } from '@utils/account-utils'
import {
  fetchProfileLocations,
  getDefaultProfileUrl
} from '@utils/profile-utils'
import { getTokenFileUrlFromZoneFile } from '@utils/zone-utils'
import { HDNode } from 'bitcoinjs-lib'
import log4js from 'log4js'
import { uploadProfile } from '../account/utils'
import { signProfileForUpload } from '@utils'
import { validateScopes, appRequestSupportsDirectHub } from './utils'
import {
  selectApi,
  selectCoreHost,
  selectCorePort,
  selectCoreAPIPassword
} from '@common/store/selectors/settings'
import {
  selectAppManifest,
  selectAppManifestLoading,
  selectAppManifestLoadingError,
  selectCoreSessionTokens
} from '@common/store/selectors/auth'
import {
  selectLocalIdentities,
  selectDefaultIdentity
} from '@common/store/selectors/profiles'

import {
  selectIdentityKeypairs,
  selectEmail,
  selectIdentityAddresses,
  selectPublicKeychain
} from '@common/store/selectors/account'
import { formatAppManifest } from '@common'

const views = [Initial, LegacyGaia]

const logger = log4js.getLogger('auth/components/AuthModal.js')

const VIEWS = {
  AUTH: 0,
  LEGACY_GAIA: 1
}
function mapStateToProps(state) {
  return {
    localIdentities: selectLocalIdentities(state),
    defaultIdentity: selectDefaultIdentity(state),
    identityKeypairs: selectIdentityKeypairs(state),
    appManifest: selectAppManifest(state),
    appManifestLoading: selectAppManifestLoading(state),
    appManifestLoadingError: selectAppManifestLoadingError(state),
    coreSessionTokens: selectCoreSessionTokens(state),
    coreHost: selectCoreHost(state),
    corePort: selectCorePort(state),
    coreAPIPassword: selectCoreAPIPassword(state),
    api: selectApi(state),
    email: selectEmail(state),
    addresses: selectIdentityAddresses(state),
    publicKeychain: selectPublicKeychain(state)
  }
}

function mapDispatchToProps(dispatch) {
  const actions = Object.assign({}, AuthActions)
  return bindActionCreators(actions, dispatch)
}

class AuthPage extends React.Component {
  static contextTypes = {
    router: PropTypes.object
  }
  static propTypes = {
    defaultIdentity: PropTypes.number.isRequired,
    localIdentities: PropTypes.array.isRequired,
    verifyAuthRequestAndLoadManifest: PropTypes.func.isRequired,
    clearSessionToken: PropTypes.func.isRequired,
    getCoreSessionToken: PropTypes.func.isRequired,
    coreAPIPassword: PropTypes.string.isRequired,
    coreSessionTokens: PropTypes.object.isRequired,
    loginToApp: PropTypes.func.isRequired,
    api: PropTypes.object.isRequired,
    identityKeypairs: PropTypes.array.isRequired,
    coreHost: PropTypes.string.isRequired,
    corePort: PropTypes.number.isRequired,
    appManifest: PropTypes.object,
    appManifestLoading: PropTypes.bool,
    email: PropTypes.string,
    noCoreSessionToken: PropTypes.func.isRequired,
    addresses: PropTypes.array.isRequired,
    publicKeychain: PropTypes.string.isRequired
  }

  constructor(props) {
    super(props)

    this.state = {
      currentIdentityIndex: this.props.defaultIdentity,
      authRequest: null,
      appManifest: null,
      coreSessionToken: null,
      decodedToken: null,
      storageConnected: this.props.api.storageConnected,
      processing: false,
      invalidScopes: false,
      sendEmail: false,
      blockchainId: null,
      noCoreStorage: false,
      responseSent: false,
      scopes: {
        email: false,
        publishData: false
      },
      view: VIEWS.AUTH
    }

    this.login = this.login.bind(this)
    this.closeModal = this.closeModal.bind(this)
  }

  componentWillMount() {
    const authRequest = getAuthRequestFromURL()
    const decodedToken = decodeToken(authRequest)
    const { scopes } = decodedToken.payload

    this.setState({
      authRequest,
      decodedToken,
      scopes: {
        ...this.state.scopes,
        email: scopes.includes('email'),
        publishData: scopes.includes('publish_data')
      }
    })

    this.props.verifyAuthRequestAndLoadManifest(authRequest)
  }

  componentWillReceiveProps(nextProps) {
    if (!this.state.responseSent) {
      const storageConnected = this.props.api.storageConnected
      this.setState({
        storageConnected
      })

      const appDomain = this.state.decodedToken.payload.domain_name
      const localIdentities = nextProps.localIdentities
      const identityKeypairs = nextProps.identityKeypairs
      if (!appDomain || !nextProps.coreSessionTokens[appDomain]) {
        if (this.state.noCoreStorage) {
          logger.debug(
            'componentWillReceiveProps: no core session token expected'
          )
        } else {
          logger.debug(
            'componentWillReceiveProps: no app domain or no core session token'
          )
          return
        }
      }

      logger.trace('componentWillReceiveProps')

      const coreSessionToken = nextProps.coreSessionTokens[appDomain]
      let decodedCoreSessionToken = null
      if (!this.state.noCoreStorage) {
        logger.debug('componentWillReceiveProps: received coreSessionToken')
        decodedCoreSessionToken = decodeToken(coreSessionToken)
      } else {
        logger.debug('componentWillReceiveProps: received no coreSessionToken')
      }

      const identityIndex = this.state.currentIdentityIndex

      const hasUsername = this.state.hasUsername
      if (hasUsername) {
        logger.debug(`login(): id index ${identityIndex} has no username`)
      }

      // Get keypair corresponding to the current user identity
      const profileSigningKeypair = identityKeypairs[identityIndex]
      const identity = localIdentities[identityIndex]

      let blockchainId = null
      if (decodedCoreSessionToken) {
        blockchainId = decodedCoreSessionToken.payload.blockchain_id
      } else {
        blockchainId = this.state.blockchainId
      }

      const profile = identity.profile
      const privateKey = profileSigningKeypair.key
      const appsNodeKey = profileSigningKeypair.appsNodeKey
      const salt = profileSigningKeypair.salt
      const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt)
      const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey()

      let profileUrlPromise

      if (identity.zoneFile && identity.zoneFile.length > 0) {
        const profileUrlFromZonefile = getTokenFileUrlFromZoneFile(
          identity.zoneFile
        )
        if (
          profileUrlFromZonefile !== null &&
          profileUrlFromZonefile !== undefined
        ) {
          profileUrlPromise = Promise.resolve(profileUrlFromZonefile)
        }
      }

      const gaiaBucketAddress = nextProps.identityKeypairs[0].address
      const identityAddress = nextProps.identityKeypairs[identityIndex].address
      const gaiaUrlBase = 'https://gaia.blockstack.org/hub'

      if (!profileUrlPromise) {
        profileUrlPromise = fetchProfileLocations(
          gaiaUrlBase,
          identityAddress,
          gaiaBucketAddress,
          identityIndex
        ).then(fetchProfileResp => {
          if (fetchProfileResp && fetchProfileResp.profileUrl) {
            return fetchProfileResp.profileUrl
          } else {
            return getDefaultProfileUrl(gaiaUrlBase, identityAddress)
          }
        })
      }

      profileUrlPromise.then(profileUrl => {
        // Add app storage bucket URL to profile if publish_data scope is requested
        if (this.state.scopes.publishData) {
          let apps = {}
          if (profile.hasOwnProperty('apps')) {
            apps = profile.apps
          }

          if (storageConnected) {
            const hubUrl = this.props.api.gaiaHubUrl
            getAppBucketUrl(hubUrl, appPrivateKey)
              .then(appBucketUrl => {
                logger.debug(
                  `componentWillReceiveProps: appBucketUrl ${appBucketUrl}`
                )
                apps[appDomain] = appBucketUrl
                logger.debug(
                  `componentWillReceiveProps: new apps array ${JSON.stringify(
                    apps
                  )}`
                )
                profile.apps = apps
                const signedProfileTokenData = signProfileForUpload(
                  profile,
                  nextProps.identityKeypairs[identityIndex]
                )
                logger.debug(
                  'componentWillReceiveProps: uploading updated profile with new apps array'
                )
                return uploadProfile(
                  this.props.api,
                  identity,
                  nextProps.identityKeypairs[identityIndex],
                  signedProfileTokenData
                )
              })
              .then(() => {
                this.completeAuthResponse(
                  privateKey,
                  blockchainId,
                  coreSessionToken,
                  appPrivateKey,
                  profile,
                  profileUrl
                )
              })
              .catch(err => {
                logger.error(
                  'componentWillReceiveProps: add app index profile not uploaded',
                  err
                )
              })
          } else {
            logger.debug(
              'componentWillReceiveProps: storage is not connected. Doing nothing.'
            )
          }
        } else {
          this.completeAuthResponse(
            privateKey,
            blockchainId,
            coreSessionToken,
            appPrivateKey,
            profile,
            profileUrl
          )
        }
      })
    } else {
      logger.error(
        'componentWillReceiveProps: response already sent - doing nothing'
      )
    }
  }

  completeAuthResponse = (
    privateKey,
    blockchainId,
    coreSessionToken,
    appPrivateKey,
    profile,
    profileUrl
  ) => {
    const appDomain = this.state.decodedToken.payload.domain_name
    const email = this.props.email
    const sendEmail = this.state.sendEmail

    logger.debug(`profileUrl: ${profileUrl}`)
    logger.debug(`email: ${email}`)

    const metadata = {
      email: sendEmail ? email : null,
      profileUrl
    }

    const doNoIncludeProfile = this.state.decodedToken.payload
      .do_not_include_profile

    const profileResponseData = doNoIncludeProfile ? null : profile

    let transitPublicKey = undefined
    let hubUrl = undefined

    let requestVersion = '0'
    if (this.state.decodedToken.payload.hasOwnProperty('version')) {
      requestVersion = this.state.decodedToken.payload.version
    }

    if (
      isLaterVersion(requestVersion, '1.1.0') &&
      this.state.decodedToken.payload.public_keys.length > 0
    ) {
      transitPublicKey = this.state.decodedToken.payload.public_keys[0]
    }
    if (appRequestSupportsDirectHub(this.state.decodedToken.payload)) {
      hubUrl = this.props.api.gaiaHubUrl
    }

    const authResponse = makeAuthResponse(
      privateKey,
      profileResponseData,
      blockchainId,
      metadata,
      coreSessionToken,
      appPrivateKey,
      undefined,
      transitPublicKey,
      hubUrl
    )

    this.props.clearSessionToken(appDomain)

    logger.trace(
      `login(): id index ${this.state.currentIdentityIndex} is logging in`
    )
    this.setState({ responseSent: true })
    redirectUserToApp(this.state.authRequest, authResponse)
  }

  closeModal() {
    this.context.router.push('/')
  }

  login = (identityIndex = this.state.currentIdentityIndex) => {
{'fraction_non_alphanumeric': 0.08355287918705306, 'fraction_numerical': 0.0012545477355413374, 'mean_word_length': 2.554927902482533, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 21, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1959616', 'n_tokens_mistral': 6504, 'n_tokens_neox': 6382, 'n_words': 1251}