<commit_before>import React, { Component, PropTypes } from 'react'
import Modal from 'react-modal'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { AuthActions } from '../store/auth'
import { Link } from 'react-router'
import { decodeToken } from 'jsontokens'
import {
  makeAuthResponse, getAuthRequestFromURL, redirectUserToApp
} from 'blockstack'
import Image from '../../components/Image'
import { AppsNode } from '../../utils/account-utils'
import { setCoreStorageConfig } from '../../utils/api-utils'
import { HDNode } from 'bitcoinjs-lib'
import { validateScopes } from '../utils'
import log4js from 'log4js'

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

function mapStateToProps(state) {
  return {
    localIdentities: state.profiles.identity.localIdentities,
    defaultIdentity: state.profiles.identity.default,
    identityKeypairs: state.account.identityAccount.keypairs,
    appManifest: state.auth.appManifest,
    appManifestLoading: state.auth.appManifestLoading,
    appManifestLoadingError: state.auth.appManifestLoadingError,
    coreSessionTokens: state.auth.coreSessionTokens,
    coreHost: state.settings.api.coreHost,
    corePort: state.settings.api.corePort,
    coreAPIPassword: state.settings.api.coreAPIPassword,
    api: state.settings.api
  }
}

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

class AuthModal extends Component {
  static contextTypes = {
    router: PropTypes.object
  }
  static propTypes = {
    defaultIdentity: PropTypes.number.isRequired,
    localIdentities: PropTypes.array.isRequired,
    loadAppManifest: 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
  }

  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
    }

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

  componentWillMount() {
    const authRequest = getAuthRequestFromURL()
    const decodedToken = decodeToken(authRequest)
    this.setState({
      authRequest,
      decodedToken
    })

    this.props.loadAppManifest(authRequest)
  }

  componentWillReceiveProps(nextProps) {
    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]) {
      logger.debug('componentWillReceiveProps: no app domain or no core session token')
      return
    }

    logger.trace('componentWillReceiveProps: received coreSessionToken')
    const coreSessionToken = nextProps.coreSessionTokens[appDomain]
    const decodedCoreSessionToken = decodeToken(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 blockchainId = decodedCoreSessionToken.payload.blockchain_id
    const identity = localIdentities[identityIndex]
    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()

    // TODO: what if the token is expired?
    // TODO: use a semver check -- or pass payload version to
    //        makeAuthResponse
    let authResponse
    if (this.state.decodedToken.payload.version === '1.1.0' &&
        this.state.decodedToken.payload.public_keys.length > 0) {
      const transitPublicKey = this.state.decodedToken.payload.public_keys[0]
      authResponse = makeAuthResponse(privateKey, profile, blockchainId,
                                      coreSessionToken, appPrivateKey,
                                      undefined, transitPublicKey)
    } else {
      authResponse = makeAuthResponse(privateKey, profile, blockchainId,
                                      coreSessionToken, appPrivateKey)
    }

    this.props.clearSessionToken(appDomain)

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

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

  login() {
    this.setState({
      processing: true,
      invalidScopes: false
    })
    this.props.loginToApp()
    const localIdentities = this.props.localIdentities
    const identityIndex = this.state.currentIdentityIndex
    const identity = localIdentities[identityIndex]
    let hasUsername = true
    if (!identity.username || identity.usernamePending) {
      logger.debug(`login(): the id ${identity.ownerAddress} has no username`)
      hasUsername = false
    }

    // check to see if username is resolvable until we get name state managament
    // fixed in
    // https://github.com/blockstack/blockstack-browser/issues/864#issuecomment-335035037


    const lookupValue = hasUsername ? identity.username : ''


    // if profile has no name, lookupUrl will be
    // http://localhost:6270/v1/names/ which returns 401
    const lookupUrl = this.props.api.nameLookupUrl.replace('{name}', lookupValue)
    fetch(lookupUrl)
        .then(response => response.text())
        .then(responseText => JSON.parse(responseText))
        .then(responseJSON => {
          if (hasUsername) {
            if (responseJSON.hasOwnProperty('address')) {
              const nameOwningAddress = responseJSON.address
              if (nameOwningAddress === identity.ownerAddress) {
                logger.debug('login: name has propagated on the network.')
              } else {
                logger.debug('login: name is not usable on the network.')
                hasUsername = false
              }
            } else {
              logger.debug('login: name is not visible on the network.')
              hasUsername = false
            }
          }

          // Get keypair corresponding to the current user identity
          const profileSigningKeypair = this.props.identityKeypairs
          .find((keypair) => keypair.address === identity.ownerAddress)

          const appDomain = this.state.decodedToken.payload.domain_name
          const scopes = this.state.decodedToken.payload.scopes
          const appsNodeKey = profileSigningKeypair.appsNodeKey
          const salt = profileSigningKeypair.salt
          const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt)
          const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey()
          const blockchainId = (hasUsername ? identity.username : null)

          const scopesJSONString = JSON.stringify(scopes)

          if (!validateScopes(scopes)) {
            this.setState({
              invalidScopes: true
            })
            logger.error(`login: App requesting unsupported scopes: ${scopesJSONString}`)
            return
          }

          this.setState({ hasUsername })
          logger.trace('login(): Calling setCoreStorageConfig()...')
          setCoreStorageConfig(this.props.api, identityIndex, identity.ownerAddress,
          identity.profile, profileSigningKeypair)
          .then(() => {
            logger.trace('login(): Core storage successfully configured.')
            logger.trace('login(): Calling getCoreSessionToken()...')
            this.props.getCoreSessionToken(this.props.coreHost,
                this.props.corePort, this.props.coreAPIPassword, appPrivateKey,
                appDomain, this.state.authRequest, blockchainId)
          })
        })
  }

  render() {
    const appManifest = this.props.appManifest
    const appManifestLoading = this.props.appManifestLoading
    const processing = this.state.processing
    const invalidScopes = this.state.invalidScopes
    return (
      <div className="">
        <Modal
          isOpen
          onRequestClose={this.closeModal}
          contentLabel="This is My Modal"
          shouldCloseOnOverlayClick
          style={{ overlay: { zIndex: 10 } }}
          className="container-fluid"
          portalClassName="auth-modal"
        >
          <h3>Sign In Request</h3>
          {appManifestLoading ?
            <div>
              <p>
                 Loading app details...
              </p>
            </div>
            :
            <div>
          {appManifest === null || invalidScopes ?
            <div>
              <p>
                 Invalid Sign In Request
              </p>
            </div>
            :
            <div>
              <p>
              The app "{appManifest.name}" wants to access your basic info
              </p>
            {appManifest.hasOwnProperty('icons') ?
              <p>
                <Image
                  src={appManifest.icons[0].src}
                  style={{ width: '128px', height: '128px' }}
                  fallbackSrc="/images/app-icon-hello-blockstack.png"
                />
              </p>
            : null}
            {this.props.localIdentities.length > 0 ?
              <div>
              {this.state.storageConnected ?
                <div>
                  <p>Choose a profile to log in with.</p>
                  <select
                    className="form-control profile-select"
                    onChange={
                      (event) => this.setState({
                        currentIdentityIndex: event.target.value
                      })}
                    value={this.state.currentIdentityIndex ? this.state.currentIdentityIndex : 0}
                    disabled={processing}
                  >
                    {this.props.localIdentities.map((identity, identityIndex) => (
                      <option
                        key={identityIndex}
                        value={identityIndex}
                      >
                        {identity.username ? identity.username : identity.ownerAddress}
                      </option>
                    ))}
                  </select>
                  <div>
                    <button
                      className="btn btn-primary btn-block"
                      onClick={this.login}
                      disabled={processing}
                    >
                      {processing ? 'Signing in...' : 'Approve'}
                    </button>
                    {processing ?
                      <a href="/" className="btn btn-outline-primary btn-block">
                        Cancel
                      </a>
                    :
                      <Link to="/" className="btn btn-outline-primary btn-block">
                        Deny
                      </Link>
                  }
                  </div>
                </div>
            :
                <div>
                  <p>
                    You need to
                    <Link to="/account/storage">connect your storage</Link>
                    in order to log in.
                  </p>
                </div>
            }
              </div>
            :
              <div>
                <p>
                  You need to <Link to="/profiles">create a Blockstack ID</Link> in order to log in.
                </p>
              </div>
            }
            </div>
        }
            </div>
           }
        </Modal>
      </div>
    )
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(AuthModal)
<commit_msg>pass profile url for 1.1.0 authentications<commit_after>import React, { Component, PropTypes } from 'react'
import Modal from 'react-modal'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { AuthActions } from '../store/auth'
import { Link } from 'react-router'
import { decodeToken } from 'jsontokens'
import {
  makeAuthResponse, getAuthRequestFromURL, redirectUserToApp
} from 'blockstack'
import Image from '../../components/Image'
import { AppsNode } from '../../utils/account-utils'
import { setCoreStorageConfig } from '../../utils/api-utils'
import { getTokenFileUrlFromZoneFile } from '../../utils/zone-utils'
import { HDNode } from 'bitcoinjs-lib'
import { validateScopes } from '../utils'
import log4js from 'log4js'

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

function mapStateToProps(state) {
  return {
    localIdentities: state.profiles.identity.localIdentities,
    defaultIdentity: state.profiles.identity.default,
    identityKeypairs: state.account.identityAccount.keypairs,
    appManifest: state.auth.appManifest,
    appManifestLoading: state.auth.appManifestLoading,
    appManifestLoadingError: state.auth.appManifestLoadingError,
    coreSessionTokens: state.auth.coreSessionTokens,
    coreHost: state.settings.api.coreHost,
    corePort: state.settings.api.corePort,
    coreAPIPassword: state.settings.api.coreAPIPassword,
    api: state.settings.api
  }
}

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

class AuthModal extends Component {
  static contextTypes = {
    router: PropTypes.object
  }
  static propTypes = {
    defaultIdentity: PropTypes.number.isRequired,
    localIdentities: PropTypes.array.isRequired,
    loadAppManifest: 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
  }

  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
    }

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

  componentWillMount() {
    const authRequest = getAuthRequestFromURL()
    const decodedToken = decodeToken(authRequest)
    this.setState({
      authRequest,
      decodedToken
    })

    this.props.loadAppManifest(authRequest)
  }

  componentWillReceiveProps(nextProps) {
    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]) {
      logger.debug('componentWillReceiveProps: no app domain or no core session token')
      return
    }

    logger.trace('componentWillReceiveProps: received coreSessionToken')
    const coreSessionToken = nextProps.coreSessionTokens[appDomain]
    const decodedCoreSessionToken = decodeToken(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 blockchainId = decodedCoreSessionToken.payload.blockchain_id
    const identity = localIdentities[identityIndex]
    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()

    const gaiaBucketAddress = nextProps.identityKeypairs[0].address
    const profileUrlBase = `https://gaia.blockstack.org/hub/${gaiaBucketAddress}`
    let profileUrl = `${profileUrlBase}/${identityIndex}/profile.json`

    if (identity.zoneFile) {
      profileUrl = getTokenFileUrlFromZoneFile(identity.zoneFile)
    }

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

    const metadata = {
      email: null,
      profileUrl
    }

    // TODO: what if the token is expired?
    // TODO: use a semver check -- or pass payload version to
    //        makeAuthResponse
    let authResponse
    if (this.state.decodedToken.payload.version === '1.1.0' &&
        this.state.decodedToken.payload.public_keys.length > 0) {
      const transitPublicKey = this.state.decodedToken.payload.public_keys[0]

      authResponse = makeAuthResponse(privateKey, profile, blockchainId,
                                      metadata,
                                      coreSessionToken, appPrivateKey,
                                      undefined, transitPublicKey)
    } else {
      authResponse = makeAuthResponse(privateKey, profile, blockchainId,
                                      metadata,
                                      coreSessionToken, appPrivateKey)
    }

    this.props.clearSessionToken(appDomain)

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

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

  login() {
    this.setState({
      processing: true,
      invalidScopes: false
    })
    this.props.loginToApp()
    const localIdentities = this.props.localIdentities
    const identityIndex = this.state.currentIdentityIndex
    const identity = localIdentities[identityIndex]
    let hasUsername = true
    if (!identity.username || identity.usernamePending) {
      logger.debug(`login(): the id ${identity.ownerAddress} has no username`)
      hasUsername = false
    }

    // check to see if username is resolvable until we get name state managament
    // fixed in
    // https://github.com/blockstack/blockstack-browser/issues/864#issuecomment-335035037


    const lookupValue = hasUsername ? identity.username : ''


    // if profile has no name, lookupUrl will be
    // http://localhost:6270/v1/names/ which returns 401
    const lookupUrl = this.props.api.nameLookupUrl.replace('{name}', lookupValue)
    fetch(lookupUrl)
        .then(response => response.text())
        .then(responseText => JSON.parse(responseText))
        .then(responseJSON => {
          if (hasUsername) {
            if (responseJSON.hasOwnProperty('address')) {
              const nameOwningAddress = responseJSON.address
              if (nameOwningAddress === identity.ownerAddress) {
                logger.debug('login: name has propagated on the network.')
              } else {
                logger.debug('login: name is not usable on the network.')
                hasUsername = false
              }
            } else {
              logger.debug('login: name is not visible on the network.')
              hasUsername = false
            }
          }

          // Get keypair corresponding to the current user identity
          const profileSigningKeypair = this.props.identityKeypairs
          .find((keypair) => keypair.address === identity.ownerAddress)

          const appDomain = this.state.decodedToken.payload.domain_name
          const scopes = this.state.decodedToken.payload.scopes
          const appsNodeKey = profileSigningKeypair.appsNodeKey
          const salt = profileSigningKeypair.salt
          const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt)
          const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey()
          const blockchainId = (hasUsername ? identity.username : null)

          const scopesJSONString = JSON.stringify(scopes)

          if (!validateScopes(scopes)) {
            this.setState({
              invalidScopes: true
            })
            logger.error(`login: App requesting unsupported scopes: ${scopesJSONString}`)
            return
          }

          this.setState({ hasUsername })
          logger.trace('login(): Calling setCoreStorageConfig()...')
          setCoreStorageConfig(this.props.api, identityIndex, identity.ownerAddress,
          identity.profile, profileSigningKeypair)
          .then(() => {
            logger.trace('login(): Core storage successfully configured.')
            logger.trace('login(): Calling getCoreSessionToken()...')
            this.props.getCoreSessionToken(this.props.coreHost,
                this.props.corePort, this.props.coreAPIPassword, appPrivateKey,
                appDomain, this.state.authRequest, blockchainId)
          })
        })
  }

  render() {
    const appManifest = this.props.appManifest
    const appManifestLoading = this.props.appManifestLoading
    const processing = this.state.processing
    const invalidScopes = this.state.invalidScopes
    return (
      <div className="">
        <Modal
          isOpen
          onRequestClose={this.closeModal}
          contentLabel="This is My Modal"
          shouldCloseOnOverlayClick
          style={{ overlay: { zIndex: 10 } }}
          className="container-fluid"
          portalClassName="auth-modal"
        >
          <h3>Sign In Request</h3>
          {appManifestLoading ?
            <div>
              <p>
                 Loading app details...
              </p>
            </div>
            :
            <div>
          {appManifest === null || invalidScopes ?
            <div>
              <p>
                 Invalid Sign In Request
              </p>
            </div>
            :
            <div>
              <p>
              The app "{appManifest.name}" wants to access your basic info
              </p>
            {appManifest.hasOwnProperty('icons') ?
              <p>
                <Image
                  src={appManifest.icons[0].src}
                  style={{ width: '128px', height: '128px' }}
                  fallbackSrc="/images/app-icon-hello-blockstack.png"
                />
              </p>
            : null}
            {this.props.localIdentities.length > 0 ?
              <div>
              {this.state.storageConnected ?
                <div>
                  <p>Choose a profile to log in with.</p>
                  <select
                    className="form-control profile-select"
                    onChange={
                      (event) => this.setState({
                        currentIdentityIndex: event.target.value
                      })}
                    value={this.state.currentIdentityIndex ? this.state.currentIdentityIndex : 0}
                    disabled={processing}
                  >
                    {this.props.localIdentities.map((identity, identityIndex) => (
                      <option
                        key={identityIndex}
                        value={identityIndex}
                      >
                        {identity.username ? identity.username : identity.ownerAddress}
                      </option>
                    ))}
                  </select>
                  <div>
                    <button
                      className="btn btn-primary btn-block"
                      onClick={this.login}
                      disabled={processing}
                    >
                      {processing ? 'Signing in...' : 'Approve'}
                    </button>
                    {processing ?
                      <a href="/" className="btn btn-outline-primary btn-block">
                        Cancel
                      </a>
                    :
                      <Link to="/" className="btn btn-outline-primary btn-block">
                        Deny
                      </Link>
                  }
                  </div>
                </div>
            :
                <div>
                  <p>
                    You need to
                    <Link to="/account/storage">connect your storage</Link>
                    in order to log in.
                  </p>
                </div>
            }
              </div>
            :
              <div>
                <p>
                  You need to <Link to="/profiles">create a Blockstack ID</Link> in order to log in.
                </p>
              </div>
            }
            </div>
        }
            </div>
           }
        </Modal>
      </div>
    )
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(AuthModal)

{'fraction_non_alphanumeric': 0.09535499961031876, 'fraction_numerical': 0.003663003663003663, 'mean_word_length': 2.009263602251407, 'pattern_counts': {'":': 0, '<': 109, '<?xml version=': 0, '>': 127, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4114794', 'n_tokens_mistral': 6764, 'n_tokens_neox': 6568, 'n_words': 1405}