aboutsummaryrefslogtreecommitdiffstats
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
parent6bafea9bffe24d2227d269812a73a9a513d92ddf (diff)
downloadcoinos-server-91e847126c832efa25ab0bf8d6f3b7e537378fa7.tar.gz
coinos-server-91e847126c832efa25ab0bf8d6f3b7e537378fa7.zip
shuffle things around
-rw-r--r--app.js38
-rw-r--r--docker-compose.yml14
-rw-r--r--index.js83
-rw-r--r--lib/auth.js37
-rw-r--r--lib/binance.js33
-rw-r--r--lib/db.js14
-rw-r--r--lib/lightning.js0
-rw-r--r--lib/locations.js20
-rw-r--r--lib/mail.js29
-rw-r--r--lib/nostr.js80
-rw-r--r--lib/passport.js79
-rw-r--r--lib/rates.js87
-rw-r--r--lib/redis.js15
-rw-r--r--lib/register.js2
-rw-r--r--lib/send.js201
-rw-r--r--lib/sendInternal.js134
-rw-r--r--lib/sockets.js93
-rw-r--r--lib/store.js1
-rw-r--r--lib/upload.js36
-rw-r--r--lib/utils.js2
-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
37 files changed, 1009 insertions, 1309 deletions
diff --git a/app.js b/app.js
index 89ba136b..af2bd59f 100644
--- a/app.js
+++ b/app.js
@@ -1,4 +1,40 @@
+import config from "$config";
import fastify from "fastify";
+import fastifyWs from "@fastify/websocket";
+import fastifyMultipart from "@fastify/multipart";
+import fastifyStatic from "@fastify/static";
+import fastifyPassport from "@fastify/passport";
+import fastifySecureSession from "@fastify/secure-session";
+
+import path from "path";
import pino from "pino";
-export default fastify({ logger: pino(), disableRequestLogging: true, maxParamLength: 500 });
+import { jwtStrategy } from "$lib/auth";
+import { socketServer } from "$lib/sockets";
+
+const app = fastify({
+ logger: pino(),
+ disableRequestLogging: true,
+ maxParamLength: 500
+});
+
+app.register(fastifySecureSession, {
+ key: Buffer.from(config.jwt, "hex")
+});
+
+app.register(fastifyPassport.initialize());
+app.register(fastifyPassport.secureSession());
+
+fastifyPassport.use("jwt", jwtStrategy);
+
+app.register(fastifyMultipart);
+
+app.register(fastifyStatic, {
+ root: path.join("/app/uploads"),
+ prefix: "/public/"
+});
+
+app.register(fastifyWs);
+app.register(socketServer);
+
+export default app;
diff --git a/docker-compose.yml b/docker-compose.yml
index 83214b92..02432156 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -39,6 +39,20 @@ services:
volumes:
- ./data/lightningb:/root/.lightning
restart: always
+ db:
+ image: eqalpha/keydb
+ container_name: db
+ volumes:
+ - ./data/db:/data
+ restart: always
+ clc:
+ image: elementsproject/lightningd:latest
+ container_name: clc
+ environment:
+ LIGHTNINGD_NETWORK: regtest
+ volumes:
+ - ./data/lightningc:/root/.lightning
+ restart: always
rd:
image: eqalpha/keydb
container_name: rd
diff --git a/index.js b/index.js
index 640f7389..7d4aa135 100644
--- a/index.js
+++ b/index.js
@@ -1,24 +1,71 @@
-import app from "./app.js";
-import { l, err } from "$lib/logging";
+import app from "$app";
+import { auth, optional } from "$lib/auth";
+import { getLocations } from "$lib/locations";
+import { getRates, sendRates } from "$lib/rates";
+import { sendHeartbeat } from "$lib/sockets";
-import "$lib/redis";
-import "$lib/sockets";
-import "$lib/passport";
-import "$lib/upload";
-import "$lib/rates";
-import "$lib/notifications";
-import "$lib/mail";
-import "$lib/nostr";
+import email from "$routes/email";
+import locations from "$routes/locations";
+import nostr from "$routes/nostr";
+import rates from "$routes/rates";
+import invoices from "$routes/invoices";
+import users from "$routes/users";
+import payments from "$routes/payments";
+import requests from "$routes/requests";
-import "$routes/locations";
-import "$routes/invoices";
-import "$routes/payments";
-import "$routes/users";
-import "$routes/requests";
+getLocations();
+getRates();
+
+setInterval(sendRates, 1000);
+setInterval(sendHeartbeat, 2000);
+
+app.post("/email", email.send);
+
+app.get("/rate", rates.last);
+app.get("/rates", rates.index);
+
+app.get("/nostr.json", nostr.identities);
+app.get("/:pubkey/followers", nostr.followers);
+app.get("/:pubkey/follows", nostr.follows);
+app.get("/:pubkey/notes", nostr.notes);
+app.get("/event/:id", nostr.event);
+app.post("/event", nostr.broadcast);
+
+app.get("/locations", locations.list);
+
+app.get("/invoice", invoices.get);
+app.post("/invoice", optional, invoices.create);
+
+app.post("/send", auth, payments.send);
+app.get("/payments", auth, payments.list);
+app.get("/payments/:hash", auth, payments.get);
+app.post("/lightning/send", auth, payments.sendLightning);
+
+app.get("/voucher/:id", payments.voucher);
+app.post("/redeem", optional, payments.redeem);
+
+app.get("/me", auth, users.me);
+app.get("/users/:key", users.get);
+app.post("/register", users.create);
+app.post("/disable2fa", users.disable2fa);
+app.post("/2fa", auth, users.enable2fa);
+app.post("/user", auth, users.update);
+app.post("/upload/:type", auth, users.upload);
+
+app.post("/login", users.login);
+app.post("/logout", optional, users.logout);
+
+app.post("/subscribe", auth, users.subscribe);
+app.post("/password", auth, users.password);
+app.post("/otpsecret", auth, users.otpsecret);
+app.get("/contacts", auth, users.contacts);
+
+app.get("/request/:id", requests.get);
+app.get("/requests", requests.list);
+app.post("/requests", requests.create);
+app.post("/requests/delete", requests.destroy);
let host = process.env.HOST || "0.0.0.0";
let port = process.env.PORT || 3119;
-app.listen({ host, port }, (e, h) => {
- if (e) return err("Failed to start:", e.message);
-});
+app.listen({ host, port });
diff --git a/lib/auth.js b/lib/auth.js
new file mode 100644
index 00000000..9720cd83
--- /dev/null
+++ b/lib/auth.js
@@ -0,0 +1,37 @@
+import fastifyPassport from "@fastify/passport";
+import { g } from "$lib/db";
+import config from "$config";
+import jwt from "passport-jwt";
+
+export const optional = {
+ preValidation: fastifyPassport.authenticate(
+ "jwt",
+ { session: false },
+ function(req, res, err, user, info) {
+ req.user = user;
+ }
+ )
+};
+
+export const auth = {
+ preValidation: fastifyPassport.authenticate("jwt", {
+ authInfo: false,
+ session: false
+ })
+};
+
+export const requirePin = async ({ body, user }) => {
+ if (!user || (user.pin && user.pin !== body.pin))
+ throw new Error("Invalid pin");
+};
+
+export const jwtStrategy = new jwt.Strategy(
+ {
+ jwtFromRequest: jwt.ExtractJwt.fromExtractors([
+ jwt.ExtractJwt.fromAuthHeaderAsBearerToken(),
+ req => (req.cookies ? req.cookies.token : null)
+ ]),
+ secretOrKey: config.jwt
+ },
+ async (payload, next) => next(null, await g(`user:${payload.id}`))
+);
diff --git a/lib/binance.js b/lib/binance.js
deleted file mode 100644
index e583d312..00000000
--- a/lib/binance.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import app from "$app";
-import WebSocket from "ws";
-import { err } from "./logging";
-import store from "./store";
-
-export default () => {
- const binance = new WebSocket(
- "wss://stream.binance.com:9443/ws/btcusdt@ticker"
- );
-
- binance.onmessage = async function(event) {
- try {
- let msg = JSON.parse(event.data);
-
- if (!store.fx) return;
-
- Object.keys(store.fx).map(symbol => {
- store.rates[symbol] = msg.c * store.fx[symbol];
- });
-
- store.last = msg.c;
- } catch (e) {
- err("binance message error", e.message);
- }
- };
-
- binance.onerror = async function(error) {
- console.log(error);
- err("binance socket error", error.message);
- };
-
- return binance;
-};
diff --git a/lib/db.js b/lib/db.js
new file mode 100644
index 00000000..76653c81
--- /dev/null
+++ b/lib/db.js
@@ -0,0 +1,14 @@
+import { createClient } from "redis";
+
+export const db = createClient({
+ url: "redis://rd"
+});
+
+db.on("error", err => console.log("db error", err));
+
+await db.connect();
+
+export default db;
+
+export let g = async k => JSON.parse(await db.get(k));
+export let s = (k, v) => db.set(k, JSON.stringify(v));
diff --git a/lib/lightning.js b/lib/lightning.js
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/lib/lightning.js
diff --git a/lib/locations.js b/lib/locations.js
new file mode 100644
index 00000000..bd2cfb5e
--- /dev/null
+++ b/lib/locations.js
@@ -0,0 +1,20 @@
+import got from "got";
+import { s } from "$lib/db";
+
+export let getLocations = async () => {
+ try {
+ let locations = await got(
+ "https://api.btcmap.org/v2/elements?updated_since=2022-09-19"
+ ).json();
+
+ locations = locations.filter(
+ l =>
+ l["osm_json"].tags && l["osm_json"].tags["payment:coinos"] === "yes"
+ )
+
+
+ await s("locations", locations);
+ } catch (e) {}
+
+ setTimeout(getLocations, 60000);
+};
diff --git a/lib/mail.js b/lib/mail.js
deleted file mode 100644
index 966ce578..00000000
--- a/lib/mail.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import app from "$app";
-import config from "$config";
-
-app.post("/email", async (req, res) => {
- try {
- try {
- var postmark = await import("postmark");
-
- var client = new postmark.ServerClient(config.postmark);
-
- await client.sendEmail({
- Subject: req.body.subject || "Email Signup",
- HtmlBody: JSON.stringify(req.body),
- TextBody: JSON.stringify(req.body),
- MessageStream: "outbound"
- });
-
- res.send({ ok: true });
- } catch (e) {
- console.log("problem sending email", e);
- res.code(500).send(e.message);
- }
- } catch (e) {
- console.log(e);
- res.code(500).send(e.message);
- }
-});
diff --git a/lib/nostr.js b/lib/nostr.js
index 08ed8b61..93370587 100644
--- a/lib/nostr.js
+++ b/lib/nostr.js
@@ -3,16 +3,15 @@ import config from "$config";
import { Relay, RelayPool } from "nostr";
import { broadcast } from "$lib/sockets";
import store from "$lib/store";
-import redis from "$lib/redis";
+import { g, s, db } from "$lib/db";
import { nada, wait } from "$lib/utils";
export let pool;
try {
pool = RelayPool(config.relays);
-} catch(e) {
- console.log(e)
-}
-
+} catch (e) {
+ console.log(e);
+}
pool.on("open", relay => {
if (relay.url.includes(config.relay)) coinos = relay;
@@ -32,7 +31,7 @@ export let q = async (
let start = Date.now();
let seen = [];
let rejected;
- query.since = await redis.get(`since:${sub}`);
+ query.since = await g(`since:${sub}`);
if (now() - query.since < since) return r();
let done = { [sub]: [] };
@@ -56,7 +55,7 @@ export let q = async (
clearTimeout(this.timer);
delete timeouts[sub];
pool.unsubscribe(sub);
- await redis.set(`since:${sub}`, now());
+ await s(`since:${sub}`, now());
},
extend(ev) {
if (ev) {
@@ -93,7 +92,7 @@ export let q = async (
let seen = [];
let getUser = async pubkey =>
- JSON.parse(await redis.get(`user:${pubkey}`)) || {
+ JSON.parse(await g(`user:${pubkey}`)) || {
username: pubkey.substr(0, 6),
pubkey,
anon: true
@@ -141,37 +140,38 @@ pool.on("event", async (relay, sub, ev) => {
updated: ev.created_at
};
- await redis.set(`user:${pubkey}`, JSON.stringify(user));
+ await s(`user:${pubkey}`, JSON.stringify(user));
coinos.send(ev);
} else if (sub.includes("follows")) {
let pubkey = sub.split(":")[0];
- await redis.set(`${pubkey}:follows`, JSON.stringify(ev.tags));
+ await s(`${pubkey}:follows`, JSON.stringify(ev.tags));
for (let f of ev.tags) {
let [_, followPubkey] = f;
- await redis.sAdd(`${followPubkey}:followers`, pubkey);
+ await db.sAdd(`${followPubkey}:followers`, pubkey);
}
coinos.send(ev);
} else if (sub.includes("followers")) {
let followed = sub.split(":")[0];
let { pubkey } = ev;
- await redis.sAdd(`${followed}:followers`, pubkey);
+ await db.sAdd(`${followed}:followers`, pubkey);
coinos.send(ev);
} else if (sub.includes("notes")) {
let pubkey = sub.split(":")[0];
- await redis.sAdd(pubkey, ev.id);
- await redis.set(`ev:${ev.id}`, JSON.stringify(ev));
+ await db.sAdd(pubkey, ev.id);
+ await s(`ev:${ev.id}`, JSON.stringify(ev));
}
} catch (e) {
console.log(e);
}
});
-app.get("/event/:id", async (req, res) => {
+export default {
+async event (req, res) {
try {
- let event = JSON.parse(await redis.get(`ev:${req.params.id}`));
+ let event = JSON.parse(await g(`ev:${req.params.id}`));
let { pubkey } = event;
- event.user = JSON.parse(await redis.get(`user:${pubkey}`)) || {
+ event.user = JSON.parse(await g(`user:${pubkey}`)) || {
username: pubkey.substr(0, 6),
pubkey,
anon: true,
@@ -183,13 +183,13 @@ app.get("/event/:id", async (req, res) => {
} catch (e) {
res.code(500).send("Problem fetching event");
}
-});
+},
-app.get("/:pubkey/notes", async (req, res) => {
+async notes (req, res) {
try {
let { pubkey } = req.params;
- let user = JSON.parse(await redis.get(`user:${pubkey}`));
+ let user = JSON.parse(await g(`user:${pubkey}`));
if (!user) {
user = await db.User.findOne({
@@ -215,12 +215,12 @@ app.get("/:pubkey/notes", async (req, res) => {
{ since: 0 }
).catch(nada);
- await redis.set(`user:${pubkey}`, JSON.stringify(user));
+ await g(`user:${pubkey}`, JSON.stringify(user));
- let ids = await redis.sMembers(pubkey);
+ let ids = await db.sMembers(pubkey);
let events = ids.length
- ? (await redis.mGet(ids.map(k => "ev:" + k))).map(JSON.parse)
+ ? (await db.mGet(ids.map(k => "ev:" + k))).map(JSON.parse)
: [];
res.send(events.map(e => ({ ...e, user })));
@@ -228,15 +228,15 @@ app.get("/:pubkey/notes", async (req, res) => {
console.log(e);
res.code(500).send("problem fetching user events");
}
-});
+},
-app.post("/event", async (req, res) => {
+async broadcast (req, res) {
let { event } = req.body;
pool.send(["EVENT", event]);
res.send(event);
-});
+},
-app.get("/:pubkey/follows", async (req, res) => {
+async follows (req, res) {
try {
let { pubkey } = req.params;
let { tagsonly } = req.query;
@@ -252,7 +252,7 @@ app.get("/:pubkey/follows", async (req, res) => {
{ timeout: 60000, eager: 60000 }
).catch(nada);
- let tags = JSON.parse(await redis.get(`${pubkey}:follows`)) || [];
+ let tags = JSON.parse(await g(`${pubkey}:follows`)) || [];
if (tagsonly) return res.send(tags);
let follows = [];
@@ -265,7 +265,7 @@ app.get("/:pubkey/follows", async (req, res) => {
authors: [pubkey]
}).catch(nada);
- let user = JSON.parse(await redis.get(`user:${pubkey}`));
+ let user = JSON.parse(await g(`user:${pubkey}`));
if (!user)
user = {
@@ -287,14 +287,14 @@ app.get("/:pubkey/follows", async (req, res) => {
}
});
-app.get("/:pubkey/followers", async (req, res) => {
+async (req, res) followers {
try {
let { pubkey } = req.params;
let pubkeys = [
...new Set([
...(await got(`${config.nostr}/followers?pubkey=${pubkey}`).json()),
- ...(await redis.sMembers(`${pubkey}:followers`))
+ ...(await db.sMembers(`${pubkey}:followers`))
])
];
@@ -307,7 +307,7 @@ app.get("/:pubkey/followers", async (req, res) => {
).catch(nada);
for (let pubkey of pubkeys) {
- let user = JSON.parse(await redis.get(`user:${pubkey}`));
+ let user = JSON.parse(await g(`user:${pubkey}`));
if (!user || !user.updated) {
q(`${pubkey}:profile:f2`, {
limit: 1,
@@ -315,7 +315,7 @@ app.get("/:pubkey/followers", async (req, res) => {
authors: [pubkey]
}).catch(nada);
- user = JSON.parse(await redis.get(`user:${pubkey}`));
+ user = JSON.parse(await g(`user:${pubkey}`));
}
if (!user)
@@ -336,11 +336,15 @@ app.get("/:pubkey/followers", async (req, res) => {
console.log(e);
res.code(500).send(e && e.message);
}
-});
+},
-app.get('/nostr.json', async (req, res) => {
+async identities (req, res) {
res.send({
- names: { adam: "566c166f3adab0c8fba5da015b0b3bcc8eb3696b455f2a1d43bfbd97059646a8", asoltys: "566c166f3adab0c8fba5da015b0b3bcc8eb3696b455f2a1d43bfbd97059646a8" }
+ names: {
+ adam: "566c166f3adab0c8fba5da015b0b3bcc8eb3696b455f2a1d43bfbd97059646a8",
+ asoltys:
+ "566c166f3adab0c8fba5da015b0b3bcc8eb3696b455f2a1d43bfbd97059646a8"
+ }
});
-});
-
+}
+}
diff --git a/lib/passport.js b/lib/passport.js
deleted file mode 100644
index 25dc0103..00000000
--- a/lib/passport.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import app from "$app";
-import jwt from "passport-jwt";
-import fastifyPassport from "@fastify/passport";
-import fastifySecureSession from "@fastify/secure-session";
-import config from "$config";
-import fs from "fs";
-import path from "path";
-import { err } from "$lib/logging";
-import { g } from "$lib/redis";
-
-app.register(fastifySecureSession, {
- key: Buffer.from(config.jwt, "hex")
-});
-
-app.register(fastifyPassport.initialize());
-app.register(fastifyPassport.secureSession());
-
-function cookieExtractor(req) {
- if (req && req.cookies) {
- return req.cookies["token"];
- }
- return null;
-}
-
-fastifyPassport.use(
- "jwt",
- new jwt.Strategy(
- {
- jwtFromRequest: jwt.ExtractJwt.fromExtractors([
- jwt.ExtractJwt.fromAuthHeaderAsBearerToken(),
- cookieExtractor
- ]),
- secretOrKey: config.jwt
- },
- async (payload, next) => {
- try {
- let { id } = payload;
- let user = await g(`user:${id}`);
- user.haspin = !!user.pin;
- next(null, user);
- } catch (e) {
- console.log("payload", payload);
- if (!e.message.includes("locked")) {
- console.log(e);
- err("problem logging in", e);
- }
- next(null, { username: "guest" });
- }
- }
- )
-);
-
-export const optionalAuth = {
- preValidation: fastifyPassport.authenticate(
- "jwt",
- { session: false },
- function(req, res, err, user, info) {
- req.user = user;
- }
- )
-};
-
-export const adminAuth = {
- preValidation: fastifyPassport.authenticate(
- "jwt",
- { session: false },
- function(req, res, err, user, info) {
- if (!(user && user.admin)) return res.code(401).send("Unauthorized");
- req.user = user;
- }
- )
-};
-
-export const auth = {
- preValidation: fastifyPassport.authenticate("jwt", {
- authInfo: false,
- session: false
- })
-};
diff --git a/lib/rates.js b/lib/rates.js
index 8064a117..c7d6918c 100644
--- a/lib/rates.js
+++ b/lib/rates.js
@@ -1,69 +1,60 @@
-import app from "$app";
import got from "got";
import config from "$config";
-import fs from "fs";
-import binance from "./binance";
import { broadcast } from "./sockets";
import store from "./store";
import { err, warn } from "$lib/logging";
+import { g, s } from "$lib/db";
+import WebSocket from "ws";
-let b = binance();
-setInterval(() => {
- b.terminate();
- b = binance();
-}, 360000);
+let ws;
+let connect = () => {
+ if (ws) ws.terminate();
+ ws = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt@ticker");
-let fetchRates = async () => {
- let date, fx;
+ ws.onmessage = async function(event) {
+ try {
+ let msg = JSON.parse(event.data);
- try {
- ({ date, fx } = JSON.parse(fs.readFileSync("data/fx")));
- } catch (e) {
- warn("Failed to read fx file");
- }
+ if (!store.fx) return;
- let today = JSON.stringify(new Date())
- .replace(/T.*/, "")
- .replace(/"/, "");
+ Object.keys(store.fx).map(symbol => {
+ store.rates[symbol] = msg.c * store.fx[symbol];
+ });
+
+ store.last = msg.c;
+ } catch (e) {
+ err("binance message error", e.message);
+ }
+ };
+
+ ws.onerror = async function(error) {
+ err("binance socket error", error.message);
+ };
+
+ setTimeout(connect, 360000);
+ return ws;
+};
- if (date !== today) {
+export let sendRates = () => broadcast("rate", store.last);
+export let getRates = async () => {
+ connect();
+
+ let date, fx;
+ let rates = await g("rates");
+ if (rates) ({ date, fx } = rates);
+
+ if (Date.now() - date > 24 * 60 * 60 * 1000) {
try {
let { rates: fx } = await got(
`https://api.exchangerate.host/latest?base=USD&v={(new Date()).toISOString().split('T')[0]}`
).json();
- fs.writeFileSync("data/fx", JSON.stringify({ date, fx }));
+ await s("rates", { date, fx });
} catch (e) {
- try {
- let { rates: fx } = await got(
- `http://data.fixer.io/api/latest?access_key=${config.fixer}`
- ).json();
-
- let USD = fx["USD"];
- Object.keys(fx).map(k => {
- fx[k] = parseFloat((fx[k] / fx["USD"]).toFixed(6));
- });
-
- fs.writeFileSync("data/fx", JSON.stringify({ date, fx }));
- } catch (e) {
- err("error fetching rates", e.message);
- }
+ err("error fetching rates", e.message);
}
}
store.fx = fx;
- setTimeout(fetchRates, 7200000);
+ setTimeout(getRates, 7200000);
};
-fetchRates();
-
-app.get("/rate", async (req, res, next) => {
- res.send(store.last);
-});
-
-app.get("/rates", async (req, res, next) => {
- res.send(store.rates);
-});
-
-setInterval(() => {
- broadcast("rate", store.last);
-}, 1000);
diff --git a/lib/redis.js b/lib/redis.js
deleted file mode 100644
index 6aec2af7..00000000
--- a/lib/redis.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createClient } from "redis";
-
-const redis = createClient({
- url: "redis://rd"
-});
-
-redis.on("error", err => console.log("redis error", err));
-
-await redis.connect();
-
-export default redis;
-
-export let g = async k => JSON.parse(await redis.get(k));
-export let s = (k, v) => redis.set(k, JSON.stringify(v));
-export let rd = redis;
diff --git a/lib/register.js b/lib/register.js
index 8ac5355f..63dccb38 100644
--- a/lib/register.js
+++ b/lib/register.js
@@ -4,7 +4,7 @@ import { authenticator } from "otplib";
import bcrypt from "bcrypt";
import { l } from "$lib/logging";
import { v4 } from "uuid";
-import { s, g } from "$lib/redis";
+import { s, g } from "$lib/db";
import { got } from "got";
export default async (user, ip, requireChallenge) => {
diff --git a/lib/send.js b/lib/send.js
deleted file mode 100644
index f378cfc1..00000000
--- a/lib/send.js
+++ /dev/null
@@ -1,201 +0,0 @@
-import config from "$config";
-import { emit } from "$lib/sockets";
-import store from "./store";
-import {
- computeConversionFee,
- conversionFeeReceiver
-} from "../routes/lightning/conversionFee";
-import ln from "$lib/ln";
-import { l, warn, err } from "$lib/logging";
-
-export default async (amount, memo, hash, user) => {
- l("attempting lightning payment", user.username, amount, hash);
-
- // decode payreq with cl
- if (!amount || payreq.satoshis > amount) amount = payreq.satoshis;
- amount = parseInt(amount);
-
- if (store.seen.includes(hash)) {
- warn("attempted to pay a paid invoice", user.username);
- throw new Error("Invoice has been paid, can't pay again");
- }
-
- let error, fee, total, preimage, payment;
-
- let conversionFee = computeConversionFee(amount);
- let conversionFeeDeduction;
- await db.transaction(async transaction => {
- l("starting lightning send transaction");
- let account;
- if (user.account.asset === config.liquid.btcasset)
- 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: config.liquid.btcasset,
- pubkey: null,
- balance: {
- [Op.gte]: amount
- }
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- conversionFeeDeduction = Math.min(account.lightning_credits, conversionFee);
- total = amount + conversionFee - conversionFeeDeduction;
-
- if (!account || account.balance < total) {
- warn(
- "user attempting to send more than they have",
- user.id,
- amount,
- conversionFee
- );
- throw new Error("Insufficient funds");
- }
-
- await account.decrement(
- { balance: total, lightning_credits: conversionFeeDeduction },
- { transaction }
- );
- await account.reload({ transaction });
-
- l("debited account", account.id, total);
- let tokens;
- if (!payreq.satoshis) tokens = amount;
-
- let m;
- let { msatoshi, msatoshi_sent, payment_preimage } = await ln.pay(
- hash,
- tokens ? `${tokens}sats` : undefined
- );
- preimage = payment_preimage;
- fee = parseInt((msatoshi_sent - msatoshi) / 1000);
-
- if (store.seen.includes(preimage)) {
- warn("duplicate payment detected", preimage);
- throw new Error("Duplicate payment detected");
- }
-
- store.seen.push(preimage);
- store.seen.push(hash);
- });
-
- await db.transaction(async transaction => {
- l("starting lightning fee transaction");
-
- let account;
- if (user.account.asset === config.liquid.btcasset)
- 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: config.liquid.btcasset,
- pubkey: null,
- balance: {
- [Op.gte]: fee
- }
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- l("deducting fee", fee);
- await account.decrement({ balance: fee }, { transaction });
- await account.reload({ transaction });
-
- if (conversionFeeDeduction) {
- conversionFee -= conversionFeeDeduction;
- }
-
- l("conversion fee", conversionFee);
-
- let receiverAccount = await db.Account.findOne({
- where: {
- "$user.username$": conversionFeeReceiver
- },
- include: [
- {
- model: db.User,
- as: "user"
- }
- ],
- lock: transaction.LOCK.UPDATE,
- transaction
- });
-
- let fee_payment;
- let fee_payment_id = null;
- if (conversionFee) {
- await receiverAccount.increment(
- { balance: conversionFee },
- { transaction }
- );
- await receiverAccount.reload({ transaction });
- fee_payment = await db.Payment.create(
- {
- amount: conversionFee,
- fee: 0,
- memo: "Lightning conversion fee",
- account_id: receiverAccount.id,
- user_id: receiverAccount.user_id,
- rate: store.rates[receiverAccount.user.currency],
- currency: receiverAccount.user.currency,
- confirmed: true,
- received: true,
- network: "COINOS"
- },
- { transaction }
- );
- fee_payment_id = fee_payment.id;
- }
-
- payment = await db.Payment.create(
- {
- amount: -total,
- account_id: account.id,
- user_id: user.id,
- hash,
- memo,
- preimage,
- rate: store.rates[user.currency],
- currency: user.currency,
- confirmed: true,
- network: "lightning",
- fee,
- fee_payment_id
- },
- { transaction }
- );
-
- l("created payment record", payment.id);
-
- payment = payment.get({ plain: true });
- payment.account = account.get({ plain: true });
- payment.fee_payment = fee_payment && fee_payment.get({ plain: true });
-
- emit(user.username, "account", payment.account);
- emit(user.username, "payment", payment);
-
- l("sent lightning", user.username, amount, total, fee);
- });
-
- return payment;
-};
diff --git a/lib/sendInternal.js b/lib/sendInternal.js
index f801c9cd..0ba0f0b4 100644
--- a/lib/sendInternal.js
+++ b/lib/sendInternal.js
@@ -7,12 +7,12 @@ import { notify } from "$lib/notifications";
import { l, err } from "$lib/logging";
import { fail } from "$lib/utils";
import ln from "$lib/ln";
-import { g, s, rd } from "$lib/redis";
+import { g, s, db } from "$lib/db";
const { HOSTNAME: hostname } = process.env;
export default async (
- { amount, address, payreq, unconfidential, memo, username, tip },
+ { amount, hash, memo, tip },
user
) => {
amount = parseInt(amount);
@@ -20,97 +20,77 @@ export default async (
if (!amount || amount < 0) fail("Amount must be greater than zero");
let fee = 0;
- let recipient;
+ let invoice = await g(`invoice:${hash}`);
let t = async () => {
- await rd.watch(`user:${user_id}`);
- user = await g(`user:${user_id}`);
- if (user.balance < amount) throw new Error("Insufficient funds");
-
+ await db.watch(`user:${user.id}`);
+ user = await g(`user:${user.id}`);
+ if (user.balance < amount) fail("Insufficient funds");
user.balance -= amount;
- let m = await rd.multi();
+ let m = await db.multi();
await s(`user:${user.id}`, user);
- if (username && (recipient = await g(`user:${username}`))) {
- let { payment_hash: h } = await ln.decode(payreq);
- let invoice = payreq && (await g(`invoice:${h}`));
+ let p1 = {
+ hash,
+ amount: -amount,
+ memo,
+ uid: user.id,
+ rate: store.rates[user.currency],
+ currency: user.currency,
+ type: "internal",
+ created: Date.now()
+ };
+
+ await s(`payment:${hash}`, p1);
+ await db.lPush(`${user.id}:payments`, hash);
+
+ l("sent internal", user.username, -p1.amount);
+ emit(user.id, "payment", p1);
- let t = async () => {
- await rd.watch(`user:${recipient.id}`);
- recipient = await g(`user:${recipient.id}`);
+ let { uid } = invoice;
+ if (uid !== user.id) {
+ let recipient = await g(`user:${uid}`);
+
+ let u = async () => {
+ await db.watch(`user:${uid}`);
+ recipient = await g(`user:${uid}`);
recipient.balance += amount;
- let m = await rd.multi();
- await s(`user:${recipient.id}`, recipient);
- if (!(await m.exec())) t();
+ let m = await db.multi();
+ await s(`user:${uid}`, recipient);
+ if (!(await m.exec())) u();
};
- await t();
-
- if (!(await m.exec())) t();
- }
- await t();
+ await u();
- let p2 = {
- id: v4(),
- amount,
- user_id: recipient.id,
- rate: store.rates[recipient.currency],
- currency: recipient.currency,
- memo,
- type: "internal",
- with_id: user.id,
- created_at: Date.now()
- };
+ let p2 = {
+ hash,
+ amount,
+ uid: recipient.id,
+ rate: store.rates[recipient.currency],
+ currency: recipient.currency,
+ memo,
+ type: "internal",
+ with_id: user.id,
+ created: Date.now()
+ };
- if (invoice) {
let { tip } = invoice;
- params.invoice_id = invoice.id;
if (tip) {
- params.tip = tip;
- params.amount -= tip;
+ p2.tip = tip;
+ p2.amount -= tip;
}
invoice.received += amount;
- await s(`invoice:${h}`, invoice);
- }
-
- if (invoice) p2.invoice = invoice;
- await s(`payment:${p2.id}`, p2);
- await rd.lPush(`${recipient.id}:payments`, p2.id);
+ await s(`invoice:${hash}`, invoice);
+ await s(`payment:${id}`, p2);
+ await db.lPush(`${recipient.id}:payments`, id);
- emit(recipient.username, "payment", p2);
-
- l("received internal", recipient.username, amount);
- notify(recipient, `Received ${amount} sats`);
- callWebhook(invoice, p2);
- };
+ l("received internal", recipient.username, amount);
+ emit(recipient.username, "payment", p2);
+ notify(recipient, `Received ${amount} sats`);
+ callWebhook(invoice, p2);
+ }
- let p1 = {
- id: v4(),
- amount: -amount,
- memo,
- user_id: user.id,
- rate: store.rates[user.currency],
- currency: user.currency,
- with_id: recipient && recipient.id,
- hash: `#${v4().substr(0, 6)} ${
- username ? `Payment to ${username}` : "Internal Transfer"
- }`,
- type: "internal",
- created_at: Date.now()
+ if (!(await m.exec())) t();
};
-
- if (!username) {
- l("creating redeemable payment");
- params.redeemcode = v4();
- params.hash = `${hostname}/redeem/${params.redeemcode}`;
- }
-
- await s(`payment:${p1.id}`, p1);
- await rd.lPush(`${user.id}:payments`, p1.id);
-
- l("sent internal", user.username, -p1.amount);
-
- emit(user.username, "payment", p1);
-
- return p1;
+ await t();
};
diff --git a/lib/sockets.js b/lib/sockets.js
index c94c29a0..7f655570 100644
--- a/lib/sockets.js
+++ b/lib/sockets.js
@@ -1,49 +1,42 @@
-import store from "$lib/store";
-import config from "$config";
import app from "$app";
+import config from "$config";
+import store from "$lib/store";
import jwt from "jsonwebtoken";
import { v4 } from "uuid";
-import WebSocket from "ws";
-import { differenceInSeconds, subSeconds } from "date-fns";
-import fastifyWs from "@fastify/websocket";
import whitelist from "./whitelist";
import { l, err, warn } from "$lib/logging";
-import { g } from "$lib/redis";
+import { g } from "$lib/db";
+import { pick } from "$lib/utils";
const code = 1000;
-
-const wss = new WebSocket.Server({ clientTracking: false, noServer: true });
-
-const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {});
const all = {};
const subscriptions = [];
const lastSeen = {};
+const users = {};
-export const emit = (uuid, type, data) => {
- if (data.uuid !== undefined) data = pick(data, ...whitelist);
-
+export const emit = (uid, type, data) => {
if (type === "payment" && data.amount > 0) {
for (let i = subscriptions.length - 1; i >= 0; i--) {
let s = subscriptions[i];
- if (s.invoice.id === data.invoice_id) {
+ if (s.hash === data.hash) {
s.ws.send(JSON.stringify({ type, data }));
}
}
}
- if (!store.sockets[uuid]) return;
- for (let id in store.sockets[uuid]) {
- const ws = store.sockets[uuid][id];
+ if (!store.sockets[uid]) return;
+ for (let id in store.sockets[uid]) {
+ const ws = store.sockets[uid][id];
ws.send(JSON.stringify({ type, data }));
}
};
export const broadcast = (type, data) => {
const sent = [];
- for (const uuid in store.sockets) {
- for (const id in store.sockets[uuid]) {
- const ws = store.sockets[uuid][id];
+ for (const uid in store.sockets) {
+ for (const id in store.sockets[uid]) {
+ const ws = store.sockets[uid][id];
ws.send(JSON.stringify({ type, data }));
sent.push(ws.id);
}
@@ -54,14 +47,10 @@ export const broadcast = (type, data) => {
});
};
-function subtractSeconds(numOfSeconds, date = new Date()) {
- date.setSeconds(date.getSeconds() - numOfSeconds);
- return date;
-}
+export async function socketServer(app) {
+ let seconds = (n, d = new Date()) => d.setSeconds(d.getSeconds() - n) && d;
-app.register(fastifyWs);
-app.register(async function(app) {
app.get("/ws", { websocket: true }, ({ socket: ws }, req) => {
const id = v4();
ws.id = id;
@@ -85,41 +74,42 @@ app.register(async function(app) {
case "login":
let token = data;
- if (lastSeen[token] > subtractSeconds(5)) return;
+ if (lastSeen[token] > seconds(5)) return;
lastSeen[token] = new Date();
try {
if (!token || token === "null" || !jwt.decode(token))
return ws.close(code, `bad token ${token}`);
- let { id: user_id } = jwt.decode(token);
- if (!user_id) throw new Error("Invalid JWT token");
- let user = await g(`user:${user_id}`);
+ let { id: uid } = jwt.decode(token);
+ if (!uid) throw new Error("Invalid JWT token");
+ let user = await g(`user:${uid}`);
if (!user) throw new Error("User not found");
- user.haspin = !!user.pin;
- if (!store.sockets[user_id]) store.sockets[user_id] = {};
- const existing = Object.keys(store.sockets[user_id]);
+ if (!store.sockets[uid]) store.sockets[uid] = {};
+ const existing = Object.keys(store.sockets[uid]);
if (existing.length > 4) {
- const p = existing.find(sid => sid !== user_id);
- store.sockets[user_id][p].send(JSON.stringify({ type: "logout" }));
+ const p = existing.find(sid => sid !== uid);
+ store.sockets[uid][p].send(
+ JSON.stringify({ type: "logout" })
+ );
setTimeout(() => {
- if (store.sockets[user_id][p])
- store.sockets[user_id][p].close(code, "too many sockets");
+ if (store.sockets[uid][p])
+ store.sockets[uid][p].close(code, "too many sockets");
}, 5000);
}
- store.sockets[user_id][id] = ws;
- names[id] = user_id;
+ store.sockets[uid][id] = ws;
+ users[id] = uid;
ws.user = user;
ws.send(
- JSON.stringify({ type: "login", data: pick(user, ...whitelist) })
+ JSON.stringify({ type: "login", data: pick(user, whitelist) })
);
ws.send(
JSON.stringify({ type: "version", data: config.clientVersion })
);
} catch (e) {
- console.log(e)
+ console.log(e);
err("caught socket error", e.message);
if (e.message.includes("not found"))
ws.send(JSON.stringify({ type: "logout" }));
@@ -146,9 +136,9 @@ app.register(async function(app) {
ws.on("close", function(code, reason) {
try {
- const uuid = names[id];
- if (store.sockets[uuid] && store.sockets[uuid][id]) {
- delete store.sockets[uuid][id];
+ const uid = users[id];
+ if (store.sockets[uid] && store.sockets[uid][id]) {
+ delete store.sockets[uid][id];
}
if (all[id]) delete all[id];
@@ -157,16 +147,15 @@ app.register(async function(app) {
}
});
});
-});
+}
-const names = {};
-setInterval(() => {
- for (const uuid in store.sockets) {
- for (const id in store.sockets[uuid]) {
- let ws = store.sockets[uuid][id];
+export let sendHeartbeat = () => {
+ for (const uid in store.sockets) {
+ for (const id in store.sockets[uid]) {
+ let ws = store.sockets[uid][id];
ws.beats++;
- if (ws.beats > 10) ws.close(code, `${uuid} ${id} lost heartbeat`);
+ if (ws.beats > 10) ws.close(code, `${uid} ${id} lost heartbeat`);
}
}
-}, 2000);
+}
diff --git a/lib/store.js b/lib/store.js
index 6f0c1c97..bf71219e 100644
--- a/lib/store.js
+++ b/lib/store.js
@@ -2,7 +2,6 @@ export default {
addresses: {},
challenge: {},
change: [],
- exceptions: [],
issuances: {},
logins: {},
nodes: [],
diff --git a/lib/upload.js b/lib/upload.js
index 95dfe21a..b37e0bf2 100644
--- a/lib/upload.js
+++ b/lib/upload.js
@@ -1,7 +1,3 @@
-import app from "$app";
-import { auth } from "./passport";
-import fastifyMultipart from "@fastify/multipart";
-import fastifyStatic from "@fastify/static";
import { fileTypeFromStream } from "file-type";
import pump from "pump";
import Clone from "readable-stream-clone";
@@ -9,21 +5,13 @@ import fs from "fs";
import path from "path";
import sharp from "sharp";
import { pipeline } from "stream";
+import { g, s } from "$lib/db";
-app.register(fastifyMultipart);
-
-app.register(fastifyStatic, {
- root: path.join("/app/uploads"),
- prefix: "/public/"
-});
-
-app.post("/upload/:type", auth, async function(req, res) {
- try {
- let { type } = req.params;
+export default async ({ params: { type }, file, user }, res) => {
if (!["banner", "profile"].includes(type))
throw new Error("invalid upload");
- let data = await req.file();
+ let data = await file();
let s1 = new Clone(data.file);
let s2 = new Clone(data.file);
@@ -32,7 +20,7 @@ app.post("/upload/:type", auth, async function(req, res) {
if (format !== "image" && !["jpg", "jpeg", "png"].includes(ext))
throw new Error("unsupported file type");
- let name = `${req.user.uuid}-${type}.webp`;
+ let name = `${user.id}-${type}.webp`;
let path = `/app/uploads/${name}`;
let size = type === "banner" ? 1920 : 240;
@@ -43,12 +31,14 @@ app.post("/upload/:type", auth, async function(req, res) {
await new Promise(r => pipeline(s2, t, fs.createWriteStream(path), r));
- req.user[type] = true;
- await req.user.save();
+ let r = async () => {
+ await db.watch(`user:${user.id}`);
+ user = await g(`user:${user.id}`);
+ user[type] = true;
+ let m = await db.multi();
+ await s(`user:${user.id}`, user);
+ };
+ await r();
res.send(name);
- } catch (e) {
- console.log(e);
- return res.code(500).send(e);
- }
-});
+}
diff --git a/lib/utils.js b/lib/utils.js
index b556c46a..e53bf649 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -1,5 +1,5 @@
import store from "$lib/store";
-import { g } from "$lib/redis";
+import { g } from "$lib/db";
import config from "$config";
export const nada = () => {};
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}`))
+ )
+ );
}
-});
+};