aboutsummaryrefslogtreecommitdiffstats
path: root/routes
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2023-01-16 23:40:25 -0800
committerAdam Soltys <[email protected]>2023-01-16 23:40:25 -0800
commit91e847126c832efa25ab0bf8d6f3b7e537378fa7 (patch)
tree4a42b6e3bec6eb0f640ed44533b54e6d3d6a5e0e /routes
parent6bafea9bffe24d2227d269812a73a9a513d92ddf (diff)
downloadcoinos-server-91e847126c832efa25ab0bf8d6f3b7e537378fa7.tar.gz
coinos-server-91e847126c832efa25ab0bf8d6f3b7e537378fa7.zip
shuffle things around
Diffstat (limited to 'routes')
-rw-r--r--routes/email.js24
-rw-r--r--routes/events.js2
-rw-r--r--routes/invoices.js33
-rw-r--r--routes/lightning/conversionFee.js12
-rw-r--r--routes/lightning/index.js11
-rw-r--r--routes/lightning/invoice.js25
-rw-r--r--routes/lightning/parse.js19
-rw-r--r--routes/lightning/query.js19
-rw-r--r--routes/lightning/receive.js92
-rw-r--r--routes/lightning/send.js17
-rw-r--r--routes/locations.js28
-rw-r--r--routes/nostr.js161
-rw-r--r--routes/payments.js300
-rw-r--r--routes/rates.js11
-rw-r--r--routes/requests.js62
-rw-r--r--routes/send.js2
-rw-r--r--routes/users.js502
17 files changed, 628 insertions, 692 deletions
diff --git a/routes/email.js b/routes/email.js
new file mode 100644
index 00000000..529307e6
--- /dev/null
+++ b/routes/email.js
@@ -0,0 +1,24 @@
+import config from "$config";
+
+export default {
+ async send({ body }, res) {
+ try {
+ let postmark = await import("postmark");
+ let client = new postmark.ServerClient(config.postmark);
+
+ await client.sendEmail({
+ Subject: body.subject || "Email Signup",
+ HtmlBody: JSON.stringify(body),
+ TextBody: JSON.stringify(body),
+ MessageStream: "outbound"
+ });
+
+ res.send({ ok: true });
+ } catch (e) {
+ console.log("problem sending email", e);
+ res.code(500).send(e.message);
+ }
+ }
+};
diff --git a/routes/events.js b/routes/events.js
index c105dc40..cea82118 100644
--- a/routes/events.js
+++ b/routes/events.js
@@ -1,6 +1,6 @@
import app from "$app";
import db from "$db";
-import redis from "$lib/redis";
+import redis from "$lib/db";
import { coinos, pool, q } from "$lib/nostr";
import store from "$lib/store";
import { nada, wait } from "$lib/utils";
diff --git a/routes/invoices.js b/routes/invoices.js
index 10a80b90..94b0d27d 100644
--- a/routes/invoices.js
+++ b/routes/invoices.js
@@ -1,21 +1,17 @@
-import app from "$app";
import store from "$lib/store";
-import { optionalAuth } from "$lib/passport";
import { l } from "$lib/logging";
import { emit } from "$lib/sockets";
-import { rd, g, s } from "$lib/redis";
+import { db, g, s } from "$lib/db";
import ln from "$lib/ln";
-app.get("/invoice", async ({ query: { id } }, res) => {
- let invoice = await g(`invoice:${id}`);
- invoice.user = await g(`user:${invoice.user_id}`);
- res.send(invoice);
-});
+export default {
+ async get({ query: { id } }, res) {
+ let invoice = await g(`invoice:${id}`);
+ invoice.user = await g(`user:${invoice.uid}`);
+ res.send(invoice);
+ },
-app.post(
- "/invoice",
- optionalAuth,
- async ({ body: { invoice, user }, user: me }, res) => {
+ async create({ body: { invoice, user }, user: me }, res) {
let { currency, tip, amount, rate, request_id } = invoice;
if (amount < 0) throw new Error("amount out of range");
@@ -23,8 +19,8 @@ app.post(
tip = tip || 0;
if (!user) user = me;
- let user_id = await g(`user:${user.username}`);
- user = await g(`user:${user_id}`);
+ let uid = await g(`user:${user.username}`);
+ user = await g(`user:${uid}`);
if (!user) throw new Error("user not provided");
@@ -46,9 +42,10 @@ app.post(
currency,
rate,
tip,
- user_id,
+ uid,
text,
- received: 0
+ received: 0,
+ created: Date.now()
};
l(
@@ -61,7 +58,7 @@ app.post(
);
await s(`invoice:${id}`, invoice);
- await rd.lPush(`${user.id}:invoices`, id);
+ await db.lPush(`${uid}:invoices`, id);
if (request_id) {
let request = await g(`request:${request_id}`);
@@ -74,4 +71,4 @@ app.post(
res.send(invoice);
}
-);
+};
diff --git a/routes/lightning/conversionFee.js b/routes/lightning/conversionFee.js
deleted file mode 100644
index f6cf434c..00000000
--- a/routes/lightning/conversionFee.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// Code for the conversion fee
-// This is in a separate file so that the same function can be used by ./send and ./receive
-
-const conversionFeeMultiplier = 0.001;
-export const conversionFeeReceiver = "coinosfees"; // account that receives the fees
-
-/**
- * Returns the fee you have to pay for withdrawing 'amount' lightning BTC.
- */
-export function computeConversionFee(amount) {
- return Math.floor(amount * conversionFeeMultiplier);
-}
diff --git a/routes/lightning/index.js b/routes/lightning/index.js
deleted file mode 100644
index ddc10bfe..00000000
--- a/routes/lightning/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import invoice from "./invoice";
-import query from "./query";
-import send from "./send";
-import parse from "./parse";
-
-export default {
- invoice,
- query,
- send,
- parse
-};
diff --git a/routes/lightning/invoice.js b/routes/lightning/invoice.js
deleted file mode 100644
index ae974120..00000000
--- a/routes/lightning/invoice.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import config from "$config";
-import { err } from "$lib/logging";
-import ln from "$lib/ln";
-import { v4 } from "uuid";
-
-export default async (req, res) => {
- let { amount, memo, tip } = req.body;
- if (!tip) tip = 0;
- let value = amount + tip;
-
- try {
- if (!memo) memo = "coinos";
- const invoice = await ln.invoice(
- value ? `${value}sat` : "any",
- v4(),
- memo,
- 360
- );
- res.send({ text: invoice.bolt11 });
- } catch (e) {
- console.log(e);
- err("problem creating invoice", e.message);
- res.code(500).send(e.message);
- }
-};
diff --git a/routes/lightning/parse.js b/routes/lightning/parse.js
deleted file mode 100644
index 1b77019a..00000000
--- a/routes/lightning/parse.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import ln from "$lib/ln";
-import store from "$lib/store";
-
-export default async (req, res) => {
- let { payreq } = req.body;
- let hour = 1000 * 60 * 60;
- let { last } = store.nodes;
- let { nodes } = store;
-
- if (!last || last > Date.now() - hour) ({ nodes } = await ln.listnodes());
- store.nodes = nodes;
-
- let twoWeeksAgo = new Date(new Date().setDate(new Date().getDate() - 14));
- let { msatoshi, payee } = await ln.decodepay(payreq);
- let node = nodes.find((n) => n.nodeid === payee);
- let alias = node ? node.alias : payee.substr(0, 12);
-
- res.send({ alias, amount: Math.round(msatoshi / 1000) });
-};
diff --git a/routes/lightning/query.js b/routes/lightning/query.js
deleted file mode 100644
index c5456a6f..00000000
--- a/routes/lightning/query.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { emit } from "$lib/sockets";
-
-export default async (req, res) => {
- const { payreq } = req.body;
-
- const invoice = await db.Invoice.findOne({
- include: {
- attributes: ["username"],
- model: db.User,
- as: "user"
- },
- where: {
- text: payreq
- }
- });
-
- if (invoice) emit(req.user.username, "to", invoice.user);
- res.send({});
-};
diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js
deleted file mode 100644
index ffb0ad95..00000000
--- a/routes/lightning/receive.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import { v4 } from "uuid";
-import store from "$lib/store";
-import { emit } from "$lib/sockets";
-import config from "$config";
-import { notify } from "$lib/notifications";
-import { callWebhook } from "$lib/webhooks";
-import { computeConversionFee } from "./conversionFee";
-import ln from "$lib/ln";
-import { l, err, warn } from "$lib/logging";
-import { g, s, rd } from "$lib/redis";
-
-let handle = async () => {
- let {
- payment_hash,
- bolt11: request,
- pay_index,
- status,
- msatoshi_received,
- payment_preimage: secret
- } = await ln.waitanyinvoice(await g("pay_index"));
-
- await s("pay_index", pay_index);
-
- let received = Math.round(msatoshi_received / 1000);
-
- try {
- l("incoming lightning payment", received);
-
- if (!secret) return;
- let account, total, user;
-
- let invoice = await g(`invoice:${payment_hash}`);
- if (!invoice) return warn("received lightning with no invoice", request);
-
- let { text: hash, currency, memo, rate, tip, user_id } = invoice;
- let amount = parseInt(received) - tip;
- if (amount < 0) throw new Error("amount out of range");
-
- let id = v4();
- let payment = {
- id,
- user_id,
- hash,
- memo,
- amount,
- currency,
- secret,
- rate,
- tip,
- invoice_id: invoice.id,
- created_at: Date.now()
- };
-
- total = amount + tip;
-
- invoice.status = "paid";
- invoice.received += total;
- await s(`invoice:${payment_hash}`, invoice);
-
- let t = async () => {
- await rd.watch(`user:${user_id}`);
- user = await g(`user:${user_id}`);
- user.balance += total;
- let m = await rd.multi();
- await s(`user:${user.id}`, user);
- if (!(await m.exec())) t();
- };
- await t();
-
- payment.invoice = invoice;
-
- await s(`payment:${id}`, payment);
- await rd.lPush(`${user.id}:payments`, id);
-
- callWebhook(invoice, payment);
-
- emit(user.username, "payment", payment);
- notify(user, `Received ${total} SAT`);
-
- l("lightning payment received", user.username, payment.amount, payment.tip);
- } catch (e) {
- console.log(e);
- err("problem receiving lightning payment", e.message);
- }
-};
-
-let listen = async () => {
- await handle();
- listen();
-};
-
-listen();
diff --git a/routes/lightning/send.js b/routes/lightning/send.js
deleted file mode 100644
index b41d5ed0..00000000
--- a/routes/lightning/send.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { err } from "$lib/logging";
-import send from "$lib/send";
-import { requirePin } from "$lib/utils";
-
-export default async (req, res) => {
- let { amount, route, memo, payreq } = req.body;
- let { user } = req;
-
- try {
- await requirePin(req);
- res.send(await send(amount, memo, payreq, user));
- } catch (e) {
- console.log(e);
- err("problem sending lightning payment", user.username, payreq, e.message);
- res.code(500).send(e.message);
- }
-};
diff --git a/routes/locations.js b/routes/locations.js
index 1cebc883..e080a7b9 100644
--- a/routes/locations.js
+++ b/routes/locations.js
@@ -1,23 +1,7 @@
-import app from "$app";
-import redis from "$lib/redis";
-import got from "got";
+import { g } from "$lib/db";
-let locations = [];
-app.get("/locations", async (req, res) => {
- res.send({ locations });
-});
-
-let getLocations = async () => {
- try {
- let r = await got(
- "https://api.btcmap.org/v2/elements?updated_since=2022-09-19"
- ).json();
- locations = r.filter(
- l => l["osm_json"].tags && l["osm_json"].tags["payment:coinos"] === "yes"
- );
- } catch(e) {}
-
- setTimeout(getLocations, 60000);
-};
-
-getLocations();
+export default {
+async list(req, res) {
+ res.send({ locations: await g('locations') });
+}
+}
diff --git a/routes/nostr.js b/routes/nostr.js
new file mode 100644
index 00000000..3d326e94
--- /dev/null
+++ b/routes/nostr.js
@@ -0,0 +1,161 @@
+export default {
+ async event({ params: { id } }, res) {
+ let event = JSON.parse(await g(`ev:${id}`));
+ let { pubkey } = event;
+
+ event.user = JSON.parse(await g(`user:${pubkey}`)) || {
+ username: pubkey.substr(0, 6),
+ pubkey,
+ anon: true,
+ follows: [],
+ followers: []
+ };
+
+ res.send(event);
+ },
+
+ async notes({ params: { pubkey } }, res) {
+ let user = JSON.parse(await g(`user:${pubkey}`));
+
+ if (!user) {
+ user = await db.User.findOne({
+ where: { pubkey }
+ });
+ }
+
+ if (!user)
+ user = {
+ username: pubkey.substr(0, 6),
+ pubkey,
+ anon: true,
+ follows: [],
+ followers: []
+ };
+
+ let params = {
+ kinds: [1],
+ authors: [pubkey]
+ },
+ opts = { since: 0 };
+
+ q(`${pubkey}:notes`, params, opts).catch(nada);
+
+ await g(`user:${pubkey}`, JSON.stringify(user));
+
+ let ids = await db.sMembers(pubkey);
+
+ let events = ids.length
+ ? (await db.mGet(ids.map(k => "ev:" + k))).map(JSON.parse)
+ : [];
+
+ res.send(events.map(e => ({ ...e, user })));
+ },
+
+ async broadcast(req, res) {
+ let { event } = req.body;
+ pool.send(["EVENT", event]);
+ res.send(event);
+ },
+
+ async follows({ params: { pubkey }, query: { tagsOnly } }, res) {
+ let sub = `${pubkey}:follows`,
+ params = {
+ limit: 1,
+ kinds: [3],
+ authors: [pubkey]
+ },
+ opts = { timeout: 60000, eager: 60000 };
+
+ q(sub, params, opts).catch(nada);
+
+ let tags = JSON.parse(await g(`${pubkey}:follows`)) || [];
+ if (tagsonly) return res.send(tags);
+
+ let follows = [];
+ for (let f of tags) {
+ let [_, pubkey] = f;
+
+ q(`${pubkey}:profile:f1`, {
+ limit: 1,
+ kinds: [0],
+ authors: [pubkey]
+ }).catch(nada);
+
+ let user = JSON.parse(await g(`user:${pubkey}`));
+
+ if (!user)
+ user = {
+ username: pubkey.substr(0, 6),
+ pubkey,
+ anon: true
+ };
+
+ follows.push(user);
+ }
+
+ follows = uniq(follows, e => e.pubkey);
+ follows.sort((a, b) => a.username.localeCompare(b.username));
+
+ res.send(follows);
+ },
+
+ async followers({ params: { pubkey}}, res) {
+ try {
+ let pubkeys = [
+ ...new Set([
+ ...(await got(`${config.nostr}/followers?pubkey=${pubkey}`).json()),
+ ...(await db.sMembers(`${pubkey}:followers`))
+ ])
+ ];
+
+ let followers = [];
+
+ q(
+ `${pubkey}:followers`,
+ { kinds: [3], "#p": [pubkey] },
+ { timeout: 60000, eager: 60000 }
+ ).catch(nada);
+
+ for (let pubkey of pubkeys) {
+ let user = JSON.parse(await g(`user:${pubkey}`));
+ if (!user || !user.updated) {
+ q(`${pubkey}:profile:f2`, {
+ limit: 1,
+ kinds: [0],
+ authors: [pubkey]
+ }).catch(nada);
+
+ user = JSON.parse(await g(`user:${pubkey}`));
+ }
+
+ if (!user)
+ user = {
+ username: pubkey.substr(0, 6),
+ pubkey,
+ anon: true
+ };
+
+ followers.push(user);
+ }
+
+ followers = uniq(followers, e => e.pubkey);
+ followers.sort((a, b) => a.username.localeCompare(b.username));
+
+ res.send(followers);
+ } catch (e) {
+ console.log(e);
+ res.code(500).send(e && e.message);
+ }
+ },
+
+ async identities(req, res) {
+ res.send({
+ names: {
+ adam:
+ "566c166f3adab0c8fba5da015b0b3bcc8eb3696b455f2a1d43bfbd97059646a8",
+ asoltys:
+ "566c166f3adab0c8fba5da015b0b3bcc8eb3696b455f2a1d43bfbd97059646a8"
+ }
+ });
+ }
+};
diff --git a/routes/payments.js b/routes/payments.js
index b328e763..13c0cdb7 100644
--- a/routes/payments.js
+++ b/routes/payments.js
@@ -1,29 +1,19 @@
-import app from "$app";
-import config from "$config";
-import store from "$lib/store";
-import { g, rd } from "$lib/redis";
-
-import { auth, adminAuth, optionalAuth } from "$lib/passport";
-import fs from "fs";
-import { join } from "path";
-import send from "./send";
-import { err, l, warn } from "$lib/logging";
-import { fail } from "$lib/utils";
import { emit } from "$lib/sockets";
+import { db, g, s } from "$lib/db";
+import { l, err, warn } from "$lib/logging";
+import { fail } from "$lib/utils";
+import ln from "$lib/ln";
+import sendInternal from "$lib/sendInternal";
+import { requirePin } from "$lib/auth";
-import lnRoutes from "./lightning/index";
-import "./lightning/receive";
-
-app.post("/send", auth, send);
-
-app.post("/lightning/parse", lnRoutes.parse);
-app.post("/lightning/query", auth, lnRoutes.query);
-app.post("/lightning/send", auth, lnRoutes.send);
+export default {
+ async send(req, res) {
+ await requirePin(req);
+ let payment = await sendInternal(req.body, req.user);
+ res.send(payment);
+ },
-app.get(
- "/payments",
- auth,
- async ({ user: { id }, query: { start, end, limit, offset } }, res) => {
+ async list({ user: { id }, query: { start, end, limit, offset } }, res) {
if (limit) limit = parseInt(limit);
if (offset) offset = parseInt(offset);
@@ -31,144 +21,134 @@ app.get(
// if (start) where.createdAt[Op.gte] = new Date(parseInt(start));
// if (end) where.createdAt[Op.lte] = new Date(parseInt(end));
- let payments = (await rd.lRange(`${id}:payments`, 0, -1)) || [];
- payments = await Promise.all(payments.map(id => g(`payment:${id}`)));
+ let payments = (await db.lRange(`${id}:payments`, 0, -1)) || [];
+ payments = await Promise.all(payments.map(hash => g(`payment:${hash}`)));
payments = payments.filter(p => p);
res.send({ payments, total: payments.length });
- }
-);
+ },
+
+ async voucher(req, res) {
+ try {
+ const { redeemcode } = req.params;
+ let payment = await g(`voucher:${id}`);
+
+ payment = payment.get({ plain: true });
+ payment.redeemer = payment["with"];
+
+ if (!payment) fail("invalid code");
+
+ res.send(payment);
+ } catch (e) {
+ res.code(500).send(e.message);
+ }
+ },
+
+ async get({ params: { hash } }, res) {
+ res.send(await g(`payment:${hash}`));
+ },
+
+ async redeem({ body: { redeemcode } }, res) {
+ try {
+ await db.transaction(async transaction => {
+ if (!redeemcode) fail("no code provided");
+
+ let { user } = req;
+
+ const source = await db.Payment.findOne({
+ where: {
+ redeemcode: req.body.redeemcode
+ },
+ include: {
+ model: db.Account,
+ as: "account"
+ },
+ lock: transaction.LOCK.UPDATE,
+ transaction
+ });
+
+ l("redeeming", redeemcode);
-// app.get("/voucher/:redeemcode", async (req, res) => {
-// try {
-// const { redeemcode } = req.params;
-// let payment = await db.Payment.findOne({
-// where: {
-// redeemcode
-// },
-// include: { all: true }
-// });
-//
-// payment = payment.get({ plain: true });
-// payment.redeemer = payment["with"];
-//
-// if (!payment) fail("invalid code");
-//
-// res.send(payment);
-// } catch (e) {
-// res.code(500).send(e.message);
-// }
-// });
+ if (!source) fail("Invalid code");
+ if (source.redeemed) fail("Voucher has already been redeemed");
+ let { amount } = source;
+ amount = -amount;
-// let redeeming = {};
-// app.post("/redeem", optionalAuth, async function(req, res) {
-// const { redeemcode } = req.body;
-// try {
-// await db.transaction(async transaction => {
-// if (redeeming[redeemcode]) fail("redemption in progress");
-// redeeming[redeemcode] = true;
-// if (!redeemcode) fail("no code provided");
-//
-// let { user } = req;
-//
-// const source = await db.Payment.findOne({
-// where: {
-// redeemcode: req.body.redeemcode
-// },
-// include: {
-// model: db.Account,
-// as: "account"
-// },
-// lock: transaction.LOCK.UPDATE,
-// transaction
-// });
-//
-// l("redeeming", redeemcode);
-//
-// if (!source) fail("Invalid code");
-// if (source.redeemed) fail("Voucher has already been redeemed");
-// let { amount } = source;
-// amount = -amount;
-//
-// if (!user) {
-// const ip =
-// req.headers["x-forwarded-for"] || req.connection.remoteAddress;
-//
-// user = await register(
-// {
-// username: redeemcode.substr(0, 8),
-// password: ""
-// },
-// ip,
-// false
-// );
-//
-// let payload = { username: user.username };
-// let token = jwt.sign(payload, config.jwt);
-// res.cookie("token", token, {
-// expires: new Date(Date.now() + 432000000)
-// });
-//
-// delete redeeming[redeemcode];
-// return res.send({ user });
-// }
-//
-// let account = await getAccount(source.account.asset, user, transaction);
-// let { hash, memo, confirmed, fee, network } = source;
-//
-// source.redeemed = true;
-// (source.with_id = user.id), await source.save({ transaction });
-//
-// let payment = await db.Payment.create(
-// {
-// amount,
-// account_id: account.id,
-// user_id: user.id,
-// hash: "Voucher " + redeemcode,
-// memo,
-// rate: store.rates[user.currency],
-// currency: user.currency,
-// confirmed,
-// network,
-// received: true,
-// fee,
-// with_id: source.user_id
-// },
-// { transaction }
-// );
-//
-// await account.increment({ balance: amount }, { transaction });
-// await account.reload({ transaction });
-//
-// payment = payment.get({ plain: true });
-// payment.account = account.get({ plain: true });
-// emit(user.username, "payment", payment);
-// emit(user.username, "account", account);
-//
-// res.send({ payment });
-// });
-// } catch (e) {
-// delete redeeming[redeemcode];
-// console.log(e);
-// err("problem redeeming", e.message);
-// return res.code(500).send("There was a problem redeeming the voucher");
-// }
-// });
-//
-// app.post("/checkRedeemCode", auth, async function(req, res) {
-// const { redeemcode } = req.body;
-//
-// const payment = await db.Payment.findOne({ where: { redeemcode } });
-// res.send(payment);
-// });
-//
-// app.get("/payments/:hash", auth, async function(req, res) {
-// try {
-// let payment = await db.Payment.findOne({
-// where: { user_id: req.user.id, hash: req.params.hash }
-// });
-//
-// return payment.get({ plain: true });
-// } catch (e) {
-// console.log(e);
-// }
-// });
+ if (!user) {
+ const ip =
+ req.headers["x-forwarded-for"] || req.connection.remoteAddress;
+
+ user = await register(
+ {
+ username: redeemcode.substr(0, 8),
+ password: ""
+ },
+ ip,
+ false
+ );
+
+ let payload = { username: user.username };
+ let token = jwt.sign(payload, config.jwt);
+ res.cookie("token", token, {
+ expires: new Date(Date.now() + 432000000)
+ });
+
+ return res.send({ user });
+ }
+
+ let account = await getAccount(source.account.asset, user, transaction);
+ let { hash, memo, confirmed, fee, network } = source;
+
+ source.redeemed = true;
+ (source.with_id = user.id), await source.save({ transaction });
+
+ let payment = await db.Payment.create(
+ {
+ amount,
+ account_id: account.id,
+ user_id: user.id,
+ hash: "Voucher " + redeemcode,
+ memo,
+ rate: store.rates[user.currency],
+ currency: user.currency,
+ confirmed,
+ network,
+ received: true,
+ fee,
+ with_id: source.user_id
+ },
+ { transaction }
+ );
+
+ await account.increment({ balance: amount }, { transaction });
+ await account.reload({ transaction });
+
+ payment = payment.get({ plain: true });
+ payment.account = account.get({ plain: true });
+ emit(user.username, "payment", payment);
+ emit(user.username, "account", account);
+
+ res.send({ payment });
+ });
+ } catch (e) {
+ console.log(e);
+ err("problem redeeming", e.message);
+ return res.code(500).send("There was a problem redeeming the voucher");
+ }
+ },
+
+ async sendLightning({ body: { payreq } }, res) {
+ let hour = 1000 * 60 * 60;
+ let { last } = store.nodes;
+ let { nodes } = store;
+
+ if (!last || last > Date.now() - hour) ({ nodes } = await ln.listnodes());
+ store.nodes = nodes;
+
+ let twoWeeksAgo = new Date(new Date().setDate(new Date().getDate() - 14));
+ let { msatoshi, payee } = await ln.decodepay(payreq);
+ let node = nodes.find(n => n.nodeid === payee);
+ let alias = node ? node.alias : payee.substr(0, 12);
+
+ res.send({ alias, amount: Math.round(msatoshi / 1000) });
+ }
+};
diff --git a/routes/rates.js b/routes/rates.js
new file mode 100644
index 00000000..c36c7ae8
--- /dev/null
+++ b/routes/rates.js
@@ -0,0 +1,11 @@
+import store from "$lib/store";
+
+export default {
+ async last(req, res) {
+ res.send(store.last);
+ },
+
+ async index(req, res) {
+ res.send(store.rates);
+ }
+};
diff --git a/routes/requests.js b/routes/requests.js
index bea8f7fd..f62c3131 100644
--- a/routes/requests.js
+++ b/routes/requests.js
@@ -1,38 +1,34 @@
-import app from "$app";
-import { auth } from "$lib/passport";
import { emit } from "$lib/sockets";
-import { g, rd } from "$lib/redis";
+import { g, db } from "$lib/db";
-app.get("/request/:id", auth, async ({ params: { id } }, res) => {
- try {
- res.send({ request: await g(id) });
- } catch (e) {
- console.log(e);
- res.code(500).send(e.message);
- }
-});
+export default {
+ async get({ params: { id } }, res) {
+ try {
+ res.send({ request: await g(id) });
+ } catch (e) {
+ console.log(e);
+ res.code(500).send(e.message);
+ }
+ },
-app.get("/requests", auth, async ({ user: { uuid } }, res) => {
- try {
- const day = new Date(new Date().setDate(new Date().getDate() - 1));
+ async list({ user: { uuid } }, res) {
+ try {
+ const day = new Date(new Date().setDate(new Date().getDate() - 1));
- let invoices = await rd.lRange(`${uuid}:invoices`, 0, -1);
- let requests = await rd.lRange(`${uuid}:requests`, 0, -1);
+ let invoices = await db.lRange(`${uuid}:invoices`, 0, -1);
+ let requests = await db.lRange(`${uuid}:requests`, 0, -1);
- res.send({ invoices, requests });
- } catch (e) {
- console.log(e);
- res.code(500).send(e.message);
- }
-});
+ res.send({ invoices, requests });
+ } catch (e) {
+ console.log(e);
+ res.code(500).send(e.message);
+ }
+ },
-app.post(
- "/requests",
- auth,
- async (
+ async create(
{ body: { recipient, ...params }, user: { username, profile } },
res
- ) => {
+ ) {
let { id: recipient_id } = await g(`user:${recipient}`);
let request = { recipient_id, ...params };
@@ -44,14 +40,10 @@ app.post(
emit(recipient, "request", request);
res.send(request);
- }
-);
+ },
-app.post(
- "/requests/delete",
- auth,
- async ({ body: { request_id }, user: { id } }, res) => {
- await rd.lrem(`user:${id}:requests`, request_id);
+ async destroy({ body: { request_id }, user: { id } }, res) {
+ await db.lrem(`user:${id}:requests`, request_id);
res.send();
}
-);
+};
diff --git a/routes/send.js b/routes/send.js
index 3f71a496..1bd4910a 100644
--- a/routes/send.js
+++ b/routes/send.js
@@ -1,6 +1,6 @@
import { err } from "$lib/logging";
import sendInternal from "$lib/sendInternal";
-import { requirePin } from "$lib/utils";
+import { requirePin } from "$lib/auth";
export default async (req, res, next) => {
try {
diff --git a/routes/users.js b/routes/users.js
index c0324803..53b02d02 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -1,8 +1,7 @@
import app from "$app";
-import { g, s } from "$lib/redis";
+import { g, s } from "$lib/db";
import config from "$config";
import store from "$lib/store";
-import { auth, optionalAuth } from "$lib/passport";
import { nada, pick, uniq, wait } from "$lib/utils";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
@@ -13,310 +12,293 @@ import { emit } from "$lib/sockets";
import register from "$lib/register";
import { requirePin } from "$lib/utils";
import got from "got";
+import upload from "$lib/upload";
-app.get("/me", auth, async (req, res) => {
- try {
- res.send(pick(req.user, whitelist));
- } catch (e) {
- console.log("problem fetching user", e);
- res.code(500).send(e.message);
- }
-});
+export default {
+ upload,
-app.get("/users/:key", async ({ params: { key } }, res) => {
- try {
- if (key.startsWith("npub")) {
- key = Buffer.from(fromWords(decode(key).words)).toString("hex");
+ async me({ user }, res) {
+ try {
+ user.balance = await g(`balance:${user.id}`);
+ res.send(pick(user, whitelist));
+ } catch (e) {
+ console.log("problem fetching user", e);
+ res.code(500).send(e.message);
}
+ },
- let user = await g(`user:${key}`);
- if (typeof user === "string") {
- user = await g(`user:${user}`);
- }
+ async get({ params: { key } }, res) {
+ try {
+ if (key.startsWith("npub")) {
+ key = Buffer.from(fromWords(decode(key).words)).toString("hex");
+ }
- if (!user && key.length === 64) {
- user = {
- username: key.substr(0, 6),
- pubkey: key,
- anon: true
- };
- }
+ let user = await g(`user:${key}`);
+ if (typeof user === "string") {
+ user = await g(`user:${user}`);
+ }
- if (!user) return res.code(500).send("User not found");
+ if (!user && key.length === 64) {
+ user = {
+ username: key.substr(0, 6),
+ pubkey: key,
+ anon: true
+ };
+ }
- let whitelist = [
- "username",
- "banner",
- "profile",
- "address",
- "currency",
- "pubkey",
- "display",
- "prompt",
- "id"
- ];
+ if (!user) return res.code(500).send("User not found");
- res.send(pick(user, whitelist));
- } catch (e) {
- console.log(e);
- res.code(500).send(e.message);
- }
-});
+ let whitelist = [
+ "username",
+ "banner",
+ "profile",
+ "address",
+ "currency",
+ "pubkey",
+ "display",
+ "prompt",
+ "id"
+ ];
-app.post("/register", async (req, res) => {
- try {
- const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
- let { cipher, pubkey, password, username, salt } = req.body.user;
+ res.send(pick(user, whitelist));
+ } catch (e) {
+ console.log(e);
+ res.code(500).send(e.message);
+ }
+ },
- let user = {
- cipher,
- pubkey,
- password,
- username,
- salt
- };
+ async create(req, res) {
+ try {
+ const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
+ let { cipher, pubkey, password, username, salt } = req.body.user;
- user = await register(user, ip, false);
- l("registered new user", username);
- res.send(pick(user, whitelist));
- } catch (e) {
- res.code(500).send(e.message);
- }
-});
+ let user = {
+ cipher,
+ pubkey,
+ password,
+ username,
+ salt
+ };
-app.post("/disable2fa", auth, async ({ user, body: { token } }, res) => {
- let { id, twofa, username, otpsecret } = user;
- if (twofa && !authenticator.check(token, otpsecret)) {
- return res.code(401).send("2fa required");
- }
+ user = await register(user, ip, false);
+ l("registered new user", username);
+ res.send(pick(user, whitelist));
+ } catch (e) {
+ res.code(500).send(e.message);
+ }
+ },
- user.twofa = false;
- await s(`user:${id}`, user);
- emit(username, "user", user);
- emit(username, "otpsecret", user.otpsecret);
- l("disabled 2fa", username);
- res.send({});
-});
+ async disable2fa({ user, body: { token } }, res) {
+ let { id, twofa, username, otpsecret } = user;
+ if (twofa && !authenticator.check(token, otpsecret)) {
+ return res.code(401).send("2fa required");
+ }
-app.post("/2fa", auth, async ({ user, body: { token } }, res) => {
- let { id, otpsecret, username } = user;
- const isValid = authenticator.check(token, otpsecret);
- if (isValid) {
- user.twofa = true;
+ user.twofa = false;
await s(`user:${id}`, user);
emit(username, "user", user);
- } else {
- return res.code(500).send("Invalid token");
- }
-
- l("enabled 2fa", username);
- res.send({});
-});
-
-app.post("/user", auth, async ({ user, body: { confirm, password, pin, newpin, username }}, res) => {
- try {
- let { user } = req;
-
- l("updating user", user.username);
-
- if (user.pin && !(pin === user.pin)) throw new Error("Pin required");
- if (typeof newpin !== "undefined") user.pin = newpin;
- if (!user.pin) user.pin = null;
-
- let exists;
- if (username) exists = await g(`user:${username}`);
+ emit(username, "otpsecret", user.otpsecret);
+ l("disabled 2fa", username);
+ res.send({});
+ },
- let token;
- if (user.username !== username && exists) {
- err("username taken", username, user.username, exists.username);
- throw new Error("Username taken");
- } else if (username) {
- if (user.username !== username)
- l("changing username", user.username, username);
- user.username = username;
+ async enable2fa({ user, body: { token } }, res) {
+ let { id, otpsecret, username } = user;
+ const isValid = authenticator.check(token, otpsecret);
+ if (isValid) {
+ user.twofa = true;
+ await s(`user:${id}`, user);
+ emit(username, "user", user);
+ } else {
+ return res.code(500).send("Invalid token");
}
- let attributes = [
- "address",
- "cipher",
- "currencies",
- "currency",
- "display",
- "email",
- "fiat",
- "locktime",
- "prompt",
- "pubkey",
- "salt",
- "seed",
- "tokens",
- "twofa",
- "unit"
- ];
+ l("enabled 2fa", username);
+ res.send({});
+ },
- for (let a of attributes) {
- if (req.body[a]) user[a] = req.body[a];
- }
-
- if (password && password === confirm) {
- user.password = await bcrypt.hash(password, 1);
- }
+ async update(
+ { user, body: { confirm, password, pin, newpin, username } },
+ res
+ ) {
+ try {
+ let { user } = req;
- await user.save();
+ l("updating user", user.username);
- user = user.get({ plain: true });
- user.haspin = !!user.pin;
+ if (user.pin && !(pin === user.pin)) throw new Error("Pin required");
+ if (typeof newpin !== "undefined") user.pin = newpin;
+ if (!user.pin) user.pin = null;
- emit(user.username, "user", user);
- res.send({ user, token });
- } catch (e) {
- console.log(e);
- err("error updating user", e.message);
- res.code(500).send(e.message);
- }
-});
+ let exists;
+ if (username) exists = await g(`user:${username}`);
-let login = async (req, res) => {
- try {
- const { params, sig, key, password, username, token: twofa } = req.body;
- l("logging in", username);
+ let token;
+ if (user.username !== username && exists) {
+ err("username taken", username, user.username, exists.username);
+ throw new Error("Username taken");
+ } else if (username) {
+ if (user.username !== username)
+ l("changing username", user.username, username);
+ user.username = username;
+ }
- if (sig) {
- const { callback } = params;
+ let attributes = [
+ "address",
+ "cipher",
+ "currencies",
+ "currency",
+ "display",
+ "email",
+ "fiat",
+ "locktime",
+ "prompt",
+ "pubkey",
+ "salt",
+ "seed",
+ "tokens",
+ "twofa",
+ "unit"
+ ];
- try {
- const url = `${callback}&sig=${sig}&key=${key}`;
- const response = await got(url).json();
- res.send(response);
- } catch (e) {
- err("problem calling lnurl login", e.message);
- res.code(500).send(e.message);
+ for (let a of attributes) {
+ if (req.body[a]) user[a] = req.body[a];
}
- return;
- }
+ if (password && password === confirm) {
+ user.password = await bcrypt.hash(password, 1);
+ }
- let id = await g(`user:${username}`);
- let user = await g(`user:${id}`);
+ await s(`user:${user.id}`, user);
- if (
- !user ||
- (user.password && !(await bcrypt.compare(password, user.password)))
- ) {
- warn("invalid username or password attempt", username);
- return res.code(401).send({});
- }
+ user.haspin = !!user.pin;
- if (
- user.twofa &&
- (typeof twofa === "undefined" ||
- !authenticator.check(twofa, user.otpsecret))
- ) {
- return res.code(401).send("2fa required");
+ emit(user.username, "user", user);
+ res.send({ user, token });
+ } catch (e) {
+ console.log(e);
+ err("error updating user", e.message);
+ res.code(500).send(e.message);
}
+ },
- l(
- "login",
- username,
- req.headers["x-forwarded-for"] || req.socket.remoteAddress
- );
+ async login(req, res) {
+ try {
+ const { params, sig, key, password, username, token: twofa } = req.body;
+ l("logging in", username);
- let payload = { username, id: user.id };
- let token = jwt.sign(payload, config.jwt);
- res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
- user = pick(user, whitelist);
- res.send({ user, token });
- } catch (e) {
- console.log(e);
- err("login error", e.message, req.socket.remoteAddress);
- res.code(401).send({});
- }
-};
+ if (sig) {
+ const { callback } = params;
-app.post("/login", login);
+ try {
+ const url = `${callback}&sig=${sig}&key=${key}`;
+ const response = await got(url).json();
+ res.send(response);
+ } catch (e) {
+ err("problem calling lnurl login", e.message);
+ res.code(500).send(e.message);
+ }
-app.post("/logout", optionalAuth, async (req, res) => {
- let { subscription } = req.body;
- if (!subscription) return res.send({});
+ return;
+ }
- const { username } = req.user;
+ let id = await g(`user:${username}`);
+ let user = await g(`user:${id}`);
- if (username) {
- l("logging out", username);
- let i = req.user.subscriptions.findIndex(
- s => JSON.stringify(s) === subscription
- );
- if (i > -1) {
- req.user.subscriptions.splice(i, 1);
- }
- await req.user.save();
- Object.keys(logins).map(
- k => logins[k]["username"] === username && delete logins[k]
- );
- }
+ if (
+ !user ||
+ (user.password && !(await bcrypt.compare(password, user.password)))
+ ) {
+ warn("invalid username or password attempt", username);
+ return res.code(401).send({});
+ }
- res.send({});
-});
+ if (
+ user.twofa &&
+ (typeof twofa === "undefined" ||
+ !authenticator.check(twofa, user.otpsecret))
+ ) {
+ return res.code(401).send("2fa required");
+ }
-app.get("/vapidPublicKey", function(req, res) {
- res.send(config.vapid.publicKey);
-});
+ l(
+ "login",
+ username,
+ req.headers["x-forwarded-for"] || req.socket.remoteAddress
+ );
-app.post("/subscribe", auth, async function(req, res) {
- let { subscriptions } = req.user;
- let { subscription } = req.body;
- if (!subscriptions) subscriptions = [];
- if (
- !subscriptions.find(s => JSON.stringify(s) === JSON.stringify(subscription))
- )
- subscriptions.push(subscription);
- req.user.subscriptions = subscriptions;
- l("subscribing", req.user.username);
- await req.user.save();
- res.sendStatus(201);
-});
+ let payload = { username, id: user.id };
+ let token = jwt.sign(payload, config.jwt);
+ res.cookie("token", token, { expires: new Date(Date.now() + 432000000) });
+ user = pick(user, whitelist);
+ res.send({ user, token });
+ } catch (e) {
+ console.log(e);
+ err("login error", e.message, req.socket.remoteAddress);
+ res.code(401).send({});
+ }
+ },
-app.post("/password", auth, async function(req, res) {
- const { user } = req;
- const { password } = req.body;
+ async logout(req, res) {
+ let { subscription } = req.body;
+ if (!subscription) return res.send({});
- if (!user.password) return res.send(true);
- res.send(await bcrypt.compare(password, user.password));
-});
+ const { username } = req.user;
-app.get("/invoices", auth, async function(req, res) {
- let invoices = await db.Invoice.findAll({
- where: { user_id: req.user.id }
- });
- res.send(invoices);
-});
+ if (username) {
+ l("logging out", username);
+ let i = req.user.subscriptions.findIndex(
+ s => JSON.stringify(s) === subscription
+ );
+ if (i > -1) {
+ req.user.subscriptions.splice(i, 1);
+ }
+ await req.user.save();
+ Object.keys(logins).map(
+ k => logins[k]["username"] === username && delete logins[k]
+ );
+ }
-app.post("/otpsecret", auth, async function(req, res) {
- try {
- await requirePin(req);
- let { otpsecret, username } = req.user;
- res.send({ secret: otpsecret, username });
- } catch (e) {
- res.code(500).send(e.message);
- }
-});
+ res.send({});
+ },
-app.get("/contacts", auth, async function({ user: { uuid } }, res) {
- let payments = (await g(`${uuid}:payments`)) || [];
- res.send(
- Promise.all(
- [...new Set(payments.map(p => p.with_id))].map(id => g(`user:${id}`))
+ async subscribe({ body, user }, res) {
+ let { subscriptions } = user;
+ let { subscription } = body;
+ if (!subscriptions) subscriptions = [];
+ if (
+ !subscriptions.find(
+ s => JSON.stringify(s) === JSON.stringify(subscription)
+ )
)
- );
-});
+ subscriptions.push(subscription);
+ user.subscriptions = subscriptions;
+ l("subscribing", user.username);
+ await user.save();
+ res.sendStatus(201);
+ },
+
+ async password({ body: { password }, user }, res) {
+ if (!user.password) return res.send(true);
+ res.send(await bcrypt.compare(password, user.password));
+ },
+
+ async otpsecret(req, res) {
+ try {
+ await requirePin(req);
+ let { otpsecret, username } = req.user;
+ res.send({ secret: otpsecret, username });
+ } catch (e) {
+ res.code(500).send(e.message);
+ }
+ },
-app.post("/checkPassword", auth, async function(req, res) {
- try {
- let { user } = req;
- let result = await bcrypt.compare(req.body.password, user.password);
- if (!result) throw new Error();
- res.send(result);
- } catch (e) {
- res.code(500).send("Invalid password");
+ async contacts({ user: { uuid } }, res) {
+ let payments = (await g(`${uuid}:payments`)) || [];
+ res.send(
+ Promise.all(
+ [...new Set(payments.map(p => p.with_id))].map(id => g(`user:${id}`))
+ )
+ );
}
-});
+};