aboutsummaryrefslogtreecommitdiffstats
path: root/passport.js
Commit message (Expand)AuthorAgeFilesLines
* stop using dotenv for configAdam Soltys2019-02-111-3/+2
* upgrade passport-jwt and use auth header extractorAdam Soltys2018-11-261-1/+1
* move register function out of graphql, fix socket cookiesAdam Soltys2018-03-191-1/+1
* authenticate sockets and only emit to specific subscribersAdam Soltys2018-03-021-2/+7
* keep track of users' invoicesAdam Soltys2018-02-201-27/+25
* jwt authAdam Soltys2018-01-291-5/+7
* use dotenv for configAdam Soltys2017-10-211-0/+3
* use dotenv for configAdam Soltys2017-09-091-1/+2
* cookie extractorAdam Soltys2017-04-171-5/+11
* jwt loginAdam Soltys2017-04-171-5/+9
* server refactor and jwt authAdam Soltys2017-04-141-43/+15
* Make usernames case insensitive (closes #14)Adam Soltys2016-02-141-4/+4
* Use bcryptjs instead of bcryptAdam Soltys2015-11-131-1/+1
* Precompile all coffeescriptAdam Soltys2014-07-201-0/+45
f='#n159'>159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
import config from "$config";
import db from "$db";
import { emit } from "$lib/sockets";
import store from "$lib/store";
import { callWebhook } from "$lib/webhooks";
import axios from "axios";
import { v4 } from "uuid";
import { notify } from "$lib/notifications";
import { sendLiquid } from "$routes/liquid/send";
import { l, err } from "$lib/logging";
import { fail } from "$lib/utils";

const { HOSTNAME: hostname } = process.env;

export default async (
  { amount, address, payreq, unconfidential, asset, memo, username, tip },
  user
) => {
  amount = parseInt(amount);

  if (!asset) asset = config.liquid.btcasset;

  if (!amount || amount < 0) fail("Amount must be greater than zero");

  return db.transaction(async transaction => {
    let account;
    if (user.account.asset === asset && !user.account.pubkey) {
      account = await db.Account.findOne({
        where: {
          id: user.account.id,
          pubkey: null
        },
        lock: transaction.LOCK.UPDATE,
        transaction
      });
    } else {
      account = await db.Account.findOne({
        where: {
          user_id: user.id,
          asset,
          pubkey: null
        },
        lock: transaction.LOCK.UPDATE,
        transaction
      });
    }

    if (!account) throw new Error("Account not found");

    if (account.balance < amount) {
      throw new Error("Insufficient funds");
    }

    let fee = 0;

    await account.decrement({ balance: amount }, { transaction });
    await account.reload({ transaction });

    let params, recipient, payment;

    if (username) {
      recipient = await db.User.findOne(
        {
          where: { username },
          include: {
            model: db.Account,
            as: "account"
          }
        },
        { transaction }
      );

      params = {
        where: { user_id: recipient.id },
        order: [["id", "DESC"]]
      };
      if (address) params.where.address = address;
      else if (payreq) params.where.text = payreq;
      let invoice = (address || payreq) && (await db.Invoice.findOne(params));

      let a2;
      let acc = {
        user_id: recipient.id,
        asset,
        pubkey: null
      };

      if (recipient.account.asset === asset && !recipient.account.pubkey)
        a2 = recipient.account;
      else {
        a2 = await db.Account.findOne({
          where: acc,
          lock: transaction.LOCK.UPDATE,
          transaction
        });
      }

      if (a2) {
        await a2.increment({ balance: amount }, { transaction });
        await a2.reload({ transaction });
      } else {
        let name = asset.substr(0, 6);
        let domain;
        let ticker = asset.substr(0, 3).toUpperCase();
        let precision = 8;

        if (store.assets[asset]) {
          ({ domain, ticker, precision, name } = store.assets[asset]);
        } else {
          const existing = await db.Account.findOne({
            where: {
              asset
            },
            order: [["id", "ASC"]],
            limit: 1,
            lock: transaction.LOCK.UPDATE,
            transaction
          });

          if (existing) {
            ({ domain, ticker, precision, name } = existing);
          }
        }

        acc = { ...acc, ...{ domain, ticker, precision, name } };
        acc.balance = amount;
        acc.pending = 0;
        acc.network = "liquid";
        a2 = await db.Account.create(acc, { transaction });
      }

      params = {
        amount,
        account_id: a2.id,
        user_id: recipient.id,
        rate: store.rates[recipient.currency],
        currency: recipient.currency,
        confirmed: true,
        hash: `#${v4().substr(0, 6)} Payment from ${user.username}`,
        memo,
        network: "COINOS",
        received: true,
        with_id: user.id
      };

      if (invoice) {
        let { tip } = invoice;
        params.invoice_id = invoice.id;
        if (tip) {
          params.tip = tip;
          params.amount -= tip;
        } 

        await invoice.increment({ received: amount }, { transaction });
        await invoice.reload({ transaction });

        let c = store.convert[invoice.text];
        if (c) {
          l(
            "internal payment detected for conversion",
            invoice.text,
            c.address,
            recipient.username
          );

          recipient.account = a2;

          sendLiquid({
            address: c.address,
            amount: amount - 100,
            user: recipient,
            limit: amount
          }).catch(console.log);
        }
      }

      let p2 = await db.Payment.create(params, { transaction });

      p2 = p2.get({ plain: true });
      p2.account = a2.get({ plain: true });
      if (invoice) p2.invoice = invoice.get({ plain: true });

      emit(recipient.username, "payment", p2);
      emit(recipient.username, "account", p2.account);

      l("received internal", recipient.username, amount);
      notify(recipient, `Received ${amount} ${a2.ticker} sats`);
      callWebhook(invoice, p2);
    }

    params = {
      amount: -amount,
      account_id: account.id,
      memo,
      user_id: user.id,
      rate: store.rates[user.currency],
      currency: user.currency,
      confirmed: true,
      with_id: recipient && recipient.id,
      hash: `#${v4().substr(0, 6)} ${
        username ? `Payment to ${username}` : "Internal Transfer"
      }`,
      network: "COINOS",
    };

    if (!username) {
      l("creating redeemable payment");
      params.redeemcode = v4();
      params.hash = `${hostname}/redeem/${params.redeemcode}`;
    }

    payment = await db.Payment.create(params, { transaction });

    payment = payment.get({ plain: true });
    payment.account = account.get({ plain: true });

    l("sent internal", user.username, -payment.amount);

    emit(user.username, "payment", payment);
    emit(user.username, "account", account);

    return payment;
  });
};