wallets/server.js

import omit from 'lodash/omit'
import { JsonApiServer } from '../json_api'
import { Wallet } from './wallet'

/**
 * Facilitates interaction with the wallet server.
 *
 * @class
 */
export class WalletServer extends JsonApiServer {
  /**
   * Create a new wallet server instance.
   *
   * @constructor
   * @param {ShelfNetwork} sdk Parent SDK instance.
   * @param {string} serverUrl wallet server URL.
   * @param {Object} opts
   * @param {boolean} [opts.allowHttp] Allow connecting to http servers, default: `false`. This must be set to false in production deployments!
   * @param {Object} [opts.proxy] Proxy configuration. Look [axios docs](https://github.com/axios/axios#request-config) for more info
   * @param {Object} [opts.httpBasicAuth] HTTP basic auth credentials. Look [axios docs](https://github.com/axios/axios#request-config) for more info.
   * @param {Object} [opts.customHeaders] Custom headers for request.
   * @param {boolean} [opts.withCredentials] Indicates whether or not cross-site Access-Control requests should be made using credentials.
   * @param {string} [opts.responseType='json'] Indicates the type of data that the server will respond with options are 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'.
   */
  constructor (sdk, serverUrl, opts = {}) {
    opts.alias = 'wallets'
    super(sdk, serverUrl, opts)
  }

  /**
   * Create an encrypted wallet.
   *
   * @param {Wallet} wallet User's wallet.
   * @param {string} password User's password.
   *
   * @return {Promise.<Wallet>} User's wallet.
   */
  async create (wallet, password) {
    let kdfParams = await this._getKdfParams()

    let encryptedWallet = wallet.encrypt(kdfParams, password)

    await this._makeCallBuilder()
      .appendUrlSegment('create')
      .withCredentials()
      .post({
        data: {
          type: 'wallets',
          id: encryptedWallet.id,
          attributes: omit(encryptedWallet, ['id'])
        }
      })

    return wallet
  }

  /**
   * Get user's wallet.
   *
   * @param {string} email User's email.
   * @param {string} password User's password.
   * @param {string} [accountId] User's account ID. Can be used instead of email.
   * @param {string} [platformId] User's platform ID. Must be used with email.
   *
   * @return {Promise.<Wallet>} User's email.
   */
  async get (email, password, accountId, platformId) {
    let loginParams = await this._getLoginParams({
      email,
      accountId,
      platformId
    })
    let kdfParams = JSON.parse(loginParams.kdfParams)

    let walletId = Wallet.deriveId(
      password,
      kdfParams,
      loginParams.salt
    )

    let encryptedWallet = await this._makeCallBuilder()
      .appendUrlSegment('show')
      .post({
        data: {
          type: 'wallets',
          id: walletId
        }
      })
      .then(response => response.data)

    return Wallet.fromEncrypted(
      encryptedWallet.keychainData,
      kdfParams,
      loginParams.salt,
      password
    )
  }

  /**
   * Verify if password is matching the wallet's one.
   *
   * @param {string} walletId User's wallet ID.
   * @param {string} password A password to be verified.
   * @param {string} accountId Account ID.
   */
  async checkPassword (walletId, accountId, password) {
    let loginParams = await this._getLoginParams({ accountId })
    let kdfParams = JSON.parse(loginParams.kdfParams)

    let walletIdForVerification = Wallet.deriveId(
      password,
      kdfParams,
      loginParams.salt
    )

    return walletIdForVerification === walletId
  }

  /**
   * Change user's password.
   *
   * @param {string} newPassword User's password.
   * @param {string} [wallet] A wallet to be updated.
   *
   * @return {Promise.<Wallet>} Updated wallet.
   */
  async changePassword (newPassword, wallet = null) {
    wallet = wallet || this._sdk.wallet
    let newWallet = wallet.clone()

    let kdfParams = await this._getKdfParams()
    let encrypted = newWallet.encrypt(kdfParams, newPassword)

    await this._updateWallet(encrypted, kdfParams)

    return newWallet
  }

  /**
   * Recover user's wallet.
   *
   * @param {string} accountId User's account ID.
   * @param {Keypair} newSignerKeypair New singer's keypair.
   * @param {string} newPassword New password.
   *
   * @return {Promise.<Wallet>} Updated wallet.
   */
  async recover (accountId, newSignerKeypair, newPassword) {
    let newWallet = new Wallet(newSignerKeypair, accountId)

    let kdfParams = await this._getKdfParams()
    let encrypted = newWallet.encrypt(kdfParams, newPassword)

    await this._updateWallet(encrypted, kdfParams, newWallet)

    return newWallet
  }

  _getLoginParams ({ email, accountId, platformId }) {
    platformId = platformId || this._sdk.platformId
    const query = accountId ? { accountId, platformId } : { email, platformId }

    return this._makeCallBuilder()
      .appendUrlSegment('show_login_params')
      .get(query)
      .then(response => response.data)
  }

  _getKdfParams () {
    return this._makeCallBuilder()
      .appendUrlSegment('kdf_params')
      .get()
      .then(response => response.data)
  }

  _updateWallet (encryptedWallet, kdfParams, wallet) {
    return this._makeCallBuilder()
      .withCredentials({ wallet })
      .appendUrlSegment('update')
      .post({
        data: {
          id: encryptedWallet.id,
          type: 'wallets',
          attributes: {
            accountId: encryptedWallet.accountId,
            newWalletId: encryptedWallet.id,
            keychainData: encryptedWallet.keychainData,
            salt: encryptedWallet.salt,
            kdfParams: JSON.stringify(kdfParams)
          }
        }
      })
  }
}