From fe62cadd6fb4a7e49f7d59401fc74ae0411e32a7 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 26 Aug 2024 21:17:43 -0700 Subject: typescript --- .gitignore | 5 +- bun.lockb | Bin 203172 -> 204761 bytes config.js.map | 1 + hooks.js | 2 - index.js.map | 1 + index.ts | 20 +- jsconfig.json | 9 - lib/api.js | 5 - lib/api.js.map | 1 + lib/api.ts | 5 + lib/app.js | 66 ----- lib/app.js.map | 1 + lib/app.ts | 66 +++++ lib/auth.js | 51 ---- lib/auth.js.map | 1 + lib/auth.ts | 51 ++++ lib/bitcoin.js | 3 - lib/bitcoin.js.map | 1 + lib/bitcoin.ts | 3 + lib/countries.js | 248 ----------------- lib/countries.js.map | 1 + lib/countries.ts | 248 +++++++++++++++++ lib/db.js | 142 ---------- lib/db.js.map | 1 + lib/db.ts | 142 ++++++++++ lib/ecash.js | 60 ----- lib/ecash.js.map | 1 + lib/ecash.ts | 60 +++++ lib/invoices.js | 128 --------- lib/invoices.js.map | 1 + lib/invoices.ts | 128 +++++++++ lib/lightning.js | 52 ---- lib/lightning.js.map | 1 + lib/lightning.ts | 52 ++++ lib/liquid.js | 3 - lib/liquid.js.map | 1 + lib/liquid.ts | 4 + lib/ln.js | 7 - lib/ln.js.map | 1 + lib/ln.ts | 7 + lib/locales/index.js | 5 - lib/locales/index.js.map | 1 + lib/locales/index.ts | 5 + lib/locations.js | 40 --- lib/locations.js.map | 1 + lib/locations.ts | 40 +++ lib/logging.js | 12 - lib/logging.js.map | 1 + lib/logging.ts | 12 + lib/mail.js | 44 --- lib/mail.js.map | 1 + lib/mail.ts | 44 +++ lib/migrate.js | 134 ---------- lib/migrate.js.map | 1 + lib/migrate.ts | 134 ++++++++++ lib/mqtt.js | 5 - lib/mqtt.js.map | 1 + lib/mqtt.ts | 5 + lib/nostr.js | 280 ------------------- lib/nostr.js.map | 1 + lib/nostr.ts | 280 +++++++++++++++++++ lib/notifications.js | 38 --- lib/notifications.js.map | 1 + lib/notifications.ts | 38 +++ lib/payments.js | 682 ----------------------------------------------- lib/payments.js.map | 1 + lib/payments.ts | 682 +++++++++++++++++++++++++++++++++++++++++++++++ lib/rates.js | 71 ----- lib/rates.js.map | 1 + lib/rates.ts | 71 +++++ lib/register.js | 62 ----- lib/register.js.map | 1 + lib/register.ts | 62 +++++ lib/rpc.js | 28 -- lib/rpc.js.map | 1 + lib/rpc.ts | 28 ++ lib/sockets.js | 165 ------------ lib/sockets.js.map | 1 + lib/sockets.ts | 161 +++++++++++ lib/store.js | 22 -- lib/store.js.map | 1 + lib/store.ts | 22 ++ lib/upload.js | 70 ----- lib/upload.js.map | 1 + lib/upload.ts | 70 +++++ lib/utils.js | 160 ----------- lib/utils.js.map | 1 + lib/utils.ts | 160 +++++++++++ lib/webhooks.js | 33 --- lib/webhooks.js.map | 1 + lib/webhooks.ts | 33 +++ lib/whitelist.js | 45 ---- lib/whitelist.js.map | 1 + lib/whitelist.ts | 45 ++++ loader.js | 22 -- package.json | 4 +- routes/ecash.js | 127 --------- routes/ecash.js.map | 1 + routes/ecash.ts | 127 +++++++++ routes/email.js | 61 ----- routes/email.js.map | 1 + routes/email.ts | 61 +++++ routes/info.js | 32 --- routes/info.js.map | 1 + routes/info.ts | 32 +++ routes/invoices.js | 38 --- routes/invoices.js.map | 1 + routes/invoices.ts | 38 +++ routes/items.js | 70 ----- routes/items.js.map | 1 + routes/items.ts | 70 +++++ routes/lnurl.js | 135 ---------- routes/lnurl.js.map | 1 + routes/lnurl.ts | 135 ++++++++++ routes/locations.js | 8 - routes/locations.js.map | 1 + routes/locations.ts | 8 + routes/nostr.js | 213 --------------- routes/nostr.js.map | 1 + routes/nostr.ts | 213 +++++++++++++++ routes/payments.js | 478 --------------------------------- routes/payments.js.map | 1 + routes/payments.ts | 478 +++++++++++++++++++++++++++++++++ routes/rates.js | 11 - routes/rates.js.map | 1 + routes/rates.ts | 11 + routes/requests.js | 85 ------ routes/requests.js.map | 1 + routes/requests.ts | 85 ++++++ routes/shopify.js | 41 --- routes/shopify.js.map | 1 + routes/shopify.ts | 41 +++ routes/users.js | 556 -------------------------------------- routes/users.js.map | 1 + routes/users.ts | 556 ++++++++++++++++++++++++++++++++++++++ sampledata/index.js | 33 +-- sampledata/index.js.map | 1 + sampledata/index.ts | 16 ++ tsconfig.json | 31 +++ 139 files changed, 4634 insertions(+), 4582 deletions(-) create mode 100644 config.js.map delete mode 100644 hooks.js create mode 100644 index.js.map delete mode 100644 jsconfig.json delete mode 100644 lib/api.js create mode 100644 lib/api.js.map create mode 100644 lib/api.ts delete mode 100644 lib/app.js create mode 100644 lib/app.js.map create mode 100644 lib/app.ts delete mode 100644 lib/auth.js create mode 100644 lib/auth.js.map create mode 100644 lib/auth.ts delete mode 100644 lib/bitcoin.js create mode 100644 lib/bitcoin.js.map create mode 100644 lib/bitcoin.ts delete mode 100644 lib/countries.js create mode 100644 lib/countries.js.map create mode 100644 lib/countries.ts delete mode 100644 lib/db.js create mode 100644 lib/db.js.map create mode 100644 lib/db.ts delete mode 100644 lib/ecash.js create mode 100644 lib/ecash.js.map create mode 100644 lib/ecash.ts delete mode 100644 lib/invoices.js create mode 100644 lib/invoices.js.map create mode 100644 lib/invoices.ts delete mode 100644 lib/lightning.js create mode 100644 lib/lightning.js.map create mode 100644 lib/lightning.ts delete mode 100644 lib/liquid.js create mode 100644 lib/liquid.js.map create mode 100644 lib/liquid.ts delete mode 100644 lib/ln.js create mode 100644 lib/ln.js.map create mode 100644 lib/ln.ts delete mode 100644 lib/locales/index.js create mode 100644 lib/locales/index.js.map create mode 100644 lib/locales/index.ts delete mode 100644 lib/locations.js create mode 100644 lib/locations.js.map create mode 100644 lib/locations.ts delete mode 100644 lib/logging.js create mode 100644 lib/logging.js.map create mode 100644 lib/logging.ts delete mode 100644 lib/mail.js create mode 100644 lib/mail.js.map create mode 100644 lib/mail.ts delete mode 100644 lib/migrate.js create mode 100644 lib/migrate.js.map create mode 100644 lib/migrate.ts delete mode 100644 lib/mqtt.js create mode 100644 lib/mqtt.js.map create mode 100644 lib/mqtt.ts delete mode 100644 lib/nostr.js create mode 100644 lib/nostr.js.map create mode 100644 lib/nostr.ts delete mode 100644 lib/notifications.js create mode 100644 lib/notifications.js.map create mode 100644 lib/notifications.ts delete mode 100644 lib/payments.js create mode 100644 lib/payments.js.map create mode 100644 lib/payments.ts delete mode 100644 lib/rates.js create mode 100644 lib/rates.js.map create mode 100644 lib/rates.ts delete mode 100644 lib/register.js create mode 100644 lib/register.js.map create mode 100644 lib/register.ts delete mode 100644 lib/rpc.js create mode 100644 lib/rpc.js.map create mode 100644 lib/rpc.ts delete mode 100644 lib/sockets.js create mode 100644 lib/sockets.js.map create mode 100644 lib/sockets.ts delete mode 100644 lib/store.js create mode 100644 lib/store.js.map create mode 100644 lib/store.ts delete mode 100644 lib/upload.js create mode 100644 lib/upload.js.map create mode 100644 lib/upload.ts delete mode 100644 lib/utils.js create mode 100644 lib/utils.js.map create mode 100644 lib/utils.ts delete mode 100644 lib/webhooks.js create mode 100644 lib/webhooks.js.map create mode 100644 lib/webhooks.ts delete mode 100644 lib/whitelist.js create mode 100644 lib/whitelist.js.map create mode 100644 lib/whitelist.ts delete mode 100644 loader.js delete mode 100644 routes/ecash.js create mode 100644 routes/ecash.js.map create mode 100644 routes/ecash.ts delete mode 100644 routes/email.js create mode 100644 routes/email.js.map create mode 100644 routes/email.ts delete mode 100644 routes/info.js create mode 100644 routes/info.js.map create mode 100644 routes/info.ts delete mode 100644 routes/invoices.js create mode 100644 routes/invoices.js.map create mode 100644 routes/invoices.ts delete mode 100644 routes/items.js create mode 100644 routes/items.js.map create mode 100644 routes/items.ts delete mode 100644 routes/lnurl.js create mode 100644 routes/lnurl.js.map create mode 100644 routes/lnurl.ts delete mode 100644 routes/locations.js create mode 100644 routes/locations.js.map create mode 100644 routes/locations.ts delete mode 100644 routes/nostr.js create mode 100644 routes/nostr.js.map create mode 100644 routes/nostr.ts delete mode 100644 routes/payments.js create mode 100644 routes/payments.js.map create mode 100644 routes/payments.ts delete mode 100644 routes/rates.js create mode 100644 routes/rates.js.map create mode 100644 routes/rates.ts delete mode 100644 routes/requests.js create mode 100644 routes/requests.js.map create mode 100644 routes/requests.ts delete mode 100644 routes/shopify.js create mode 100644 routes/shopify.js.map create mode 100644 routes/shopify.ts delete mode 100644 routes/users.js create mode 100644 routes/users.js.map create mode 100644 routes/users.ts create mode 100644 sampledata/index.js.map create mode 100644 sampledata/index.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 0676a28b..15957b79 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ node_modules/ .pnpm* data -config.js +config.ts compose.yml .env *.flitevox mimic watcher.sh .aws +*.js +routes/**/*.js +lib/**/*.js diff --git a/bun.lockb b/bun.lockb index a7cfeb39..6aaed16c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/config.js.map b/config.js.map new file mode 100644 index 00000000..f1e0c10e --- /dev/null +++ b/config.js.map @@ -0,0 +1 @@ +{"version":3,"file":"config.js","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,OAAO,EAAE,iBAAiB;IAC1B,OAAO,EAAE,kBAAkB;IAC3B,QAAQ,EAAE,iEAAiE;IAC3E,EAAE,EAAE,YAAY;IAChB,KAAK,EAAE,iBAAiB;IACxB,MAAM,EAAE;QACN,iBAAiB;QACjB,+BAA+B;QAC/B,cAAc;QACd,oBAAoB;QACpB,sBAAsB;QACtB,4BAA4B;KAC7B;IACD,GAAG,EAAE,kEAAkE;IACvE,OAAO,EAAE;QACP,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE,QAAQ;QAClB,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,KAAK;KACZ;IACD,MAAM,EAAE;QACN,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE,QAAQ;QAClB,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,kEAAkE;KACxE;IACD,SAAS,EAAE,oDAAoD;CAChE,CAAC"} \ No newline at end of file diff --git a/hooks.js b/hooks.js deleted file mode 100644 index fdb6efee..00000000 --- a/hooks.js +++ /dev/null @@ -1,2 +0,0 @@ -import { register } from "node:module"; -register("./loader.js", import.meta.url); diff --git a/index.js.map b/index.js.map new file mode 100644 index 00000000..10968013 --- /dev/null +++ b/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/index.ts b/index.ts index 7e9feb04..a0f35738 100644 --- a/index.ts +++ b/index.ts @@ -1,20 +1,17 @@ import app from "$lib/app"; import { auth, admin, optional } from "$lib/auth"; -import { fillPool } from "$lib/nostr"; import { listenForLightning } from "$lib/lightning"; import { getLocations } from "$lib/locations"; import { catchUp } from "$lib/payments"; import { getFx } from "$lib/rates"; import { sendHeartbeat } from "$lib/sockets"; -import { err } from "$lib/logging"; import ecash from "$routes/ecash"; import email from "$routes/email"; import info from "$routes/info"; import locations from "$routes/locations"; import lnurl from "$routes/lnurl"; -import nostr from "$routes/nostr"; import rates from "$routes/rates"; import invoices from "$routes/invoices"; import items from "$routes/items"; @@ -41,15 +38,6 @@ app.post("/email", email.send); app.get("/rate", rates.last); app.get("/rates", rates.index); -// app.get("/nostr/info", nostr.info); -// 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("/:pubkey/:since/messages", nostr.messages); -// app.get("/event/:id", nostr.event); -// app.post("/event", nostr.broadcast); - app.get("/locations", locations.list); app.get("/invoice/:id", invoices.get); @@ -126,12 +114,12 @@ app.post("/claim", auth, ecash.claim); app.post("/mint", auth, ecash.mint); app.post("/melt", auth, ecash.melt); -let host = process.env.HOST || "0.0.0.0"; -let port = process.env.PORT || 3119; +let host = process.env["HOST"] || "0.0.0.0"; +let port = process.env["PORT"] || 3119; -app.listen({ host, port }, console.log); +app.listen({ host, port }); -let logerr = (e) => +let logerr = (e: Error) => e.message.includes("Invalid") || e.message.includes("MASK") || e.message.includes("Rate") || diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index 659b36c8..00000000 --- a/jsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "paths": { - "$lib/*": ["./lib/*"], - "$routes/*": ["./routes/*"], - "$config": ["./config.js"] - } - } -} diff --git a/lib/api.js b/lib/api.js deleted file mode 100644 index 3fa8c35b..00000000 --- a/lib/api.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - bitcoin: "https://mempool.space/api/v1", - liquid: "https://liquid.network/api/v1", -}; - diff --git a/lib/api.js.map b/lib/api.js.map new file mode 100644 index 00000000..7b03f30a --- /dev/null +++ b/lib/api.js.map @@ -0,0 +1 @@ +{"version":3,"file":"api.js","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,OAAO,EAAE,8BAA8B;IACvC,MAAM,EAAE,+BAA+B;CACxC,CAAC"} \ No newline at end of file diff --git a/lib/api.ts b/lib/api.ts new file mode 100644 index 00000000..3fa8c35b --- /dev/null +++ b/lib/api.ts @@ -0,0 +1,5 @@ +export default { + bitcoin: "https://mempool.space/api/v1", + liquid: "https://liquid.network/api/v1", +}; + diff --git a/lib/app.js b/lib/app.js deleted file mode 100644 index 03ed3bb0..00000000 --- a/lib/app.js +++ /dev/null @@ -1,66 +0,0 @@ -import config from "$config"; -import fastify from "fastify"; -import fastifyMultipart from "@fastify/multipart"; -import fastifyStatic from "@fastify/static"; -import fastifyPassport from "@fastify/passport"; -import fastifySecureSession from "@fastify/secure-session"; -import cors from "@fastify/cors"; - -import path from "path"; -import pino from "pino"; - -import { jwtStrategy } from "$lib/auth"; - -const app = fastify({ - logger: pino(), - disableRequestLogging: true, - maxParamLength: 500, -}); - -await app.register(import("@fastify/rate-limit"), { - allowList: (req, key) => req.raw.url.includes("public"), - max: 100, - timeWindow: 2000, - keyGenerator: (req) => req.headers["cf-connecting-ip"], - errorResponseBuilder: (req, context) => { - return { - statusCode: 429, - error: "Too Many Requests", - message: "Rate limit exceeded, retry in 2 seconds", - }; - }, -}); - -app.register(fastifySecureSession, { - key: Buffer.from(config.jwt, "hex"), -}); - -app.register(fastifyPassport.initialize()); -app.register(fastifyPassport.secureSession()); - -fastifyPassport.use("jwt", jwtStrategy); - -app.register(fastifyMultipart, { limits: { fileSize: 10 ** 7 } }); - -app.register(fastifyStatic, { - root: path.join("/home/bun/app/data/uploads"), - prefix: "/public/", -}); - -await app.register(cors, { - origin: true, -}); - -app.setErrorHandler((error, request, reply) => { - if (error instanceof fastify.errorCodes.FST_ERR_BAD_STATUS_CODE) { - reply.status(500).send({ ok: false }); - } else { - reply.send(error); - } -}); - -app.setNotFoundHandler((request, reply) => { - reply.code(404).type("text/html").send("Not Found"); -}); - -export default app; diff --git a/lib/app.js.map b/lib/app.js.map new file mode 100644 index 00000000..ca7f1a68 --- /dev/null +++ b/lib/app.js.map @@ -0,0 +1 @@ +{"version":3,"file":"app.js","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAA6B;AAC7B,oDAA8B;AAC9B,iEAAkD;AAClD,2DAA4C;AAC5C,+DAAgD;AAChD,2EAA2D;AAC3D,uDAAiC;AAEjC,8CAAwB;AACxB,8CAAwB;AAExB,kCAAwC;AAExC,IAAM,GAAG,GAAG,IAAA,iBAAO,EAAC;IAClB,MAAM,EAAE,IAAA,cAAI,GAAE;IACd,qBAAqB,EAAE,IAAI;IAC3B,cAAc,EAAE,GAAG;CACpB,CAAC,CAAC;AAEH,MAAM,GAAG,CAAC,QAAQ,kEAAQ,qBAAqB,QAAG;IAChD,SAAS,EAAE,UAAC,GAAG,EAAE,GAAG,IAAK,OAAA,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAA9B,CAA8B;IACvD,GAAG,EAAE,GAAG;IACR,UAAU,EAAE,IAAI;IAChB,YAAY,EAAE,UAAC,GAAG,IAAK,OAAA,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAA/B,CAA+B;IACtD,oBAAoB,EAAE,UAAC,GAAG,EAAE,OAAO;QACjC,OAAO;YACL,UAAU,EAAE,GAAG;YACf,KAAK,EAAE,mBAAmB;YAC1B,OAAO,EAAE,yCAAyC;SACnD,CAAC;IACJ,CAAC;CACF,CAAC,CAAC;AAEH,GAAG,CAAC,QAAQ,CAAC,wBAAoB,EAAE;IACjC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,iBAAM,CAAC,GAAG,EAAE,KAAK,CAAC;CACpC,CAAC,CAAC;AAEH,GAAG,CAAC,QAAQ,CAAC,kBAAe,CAAC,UAAU,EAAE,CAAC,CAAC;AAC3C,GAAG,CAAC,QAAQ,CAAC,kBAAe,CAAC,aAAa,EAAE,CAAC,CAAC;AAE9C,kBAAe,CAAC,GAAG,CAAC,KAAK,EAAE,kBAAW,CAAC,CAAC;AAExC,GAAG,CAAC,QAAQ,CAAC,mBAAgB,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,SAAA,EAAE,EAAI,CAAC,CAAA,EAAE,EAAE,CAAC,CAAC;AAElE,GAAG,CAAC,QAAQ,CAAC,gBAAa,EAAE;IAC1B,IAAI,EAAE,cAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC;IAC7C,MAAM,EAAE,UAAU;CACnB,CAAC,CAAC;AAEH,MAAM,GAAG,CAAC,QAAQ,CAAC,cAAI,EAAE;IACvB,MAAM,EAAE,IAAI;CACb,CAAC,CAAC;AAEH,GAAG,CAAC,eAAe,CAAC,UAAC,KAAK,EAAE,OAAO,EAAE,KAAK;IACxC,IAAI,KAAK,YAAY,iBAAO,CAAC,UAAU,CAAC,uBAAuB,EAAE,CAAC;QAChE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACxC,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,kBAAkB,CAAC,UAAC,OAAO,EAAE,KAAK;IACpC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC;AAEH,kBAAe,GAAG,CAAC"} \ No newline at end of file diff --git a/lib/app.ts b/lib/app.ts new file mode 100644 index 00000000..03ed3bb0 --- /dev/null +++ b/lib/app.ts @@ -0,0 +1,66 @@ +import config from "$config"; +import fastify from "fastify"; +import fastifyMultipart from "@fastify/multipart"; +import fastifyStatic from "@fastify/static"; +import fastifyPassport from "@fastify/passport"; +import fastifySecureSession from "@fastify/secure-session"; +import cors from "@fastify/cors"; + +import path from "path"; +import pino from "pino"; + +import { jwtStrategy } from "$lib/auth"; + +const app = fastify({ + logger: pino(), + disableRequestLogging: true, + maxParamLength: 500, +}); + +await app.register(import("@fastify/rate-limit"), { + allowList: (req, key) => req.raw.url.includes("public"), + max: 100, + timeWindow: 2000, + keyGenerator: (req) => req.headers["cf-connecting-ip"], + errorResponseBuilder: (req, context) => { + return { + statusCode: 429, + error: "Too Many Requests", + message: "Rate limit exceeded, retry in 2 seconds", + }; + }, +}); + +app.register(fastifySecureSession, { + key: Buffer.from(config.jwt, "hex"), +}); + +app.register(fastifyPassport.initialize()); +app.register(fastifyPassport.secureSession()); + +fastifyPassport.use("jwt", jwtStrategy); + +app.register(fastifyMultipart, { limits: { fileSize: 10 ** 7 } }); + +app.register(fastifyStatic, { + root: path.join("/home/bun/app/data/uploads"), + prefix: "/public/", +}); + +await app.register(cors, { + origin: true, +}); + +app.setErrorHandler((error, request, reply) => { + if (error instanceof fastify.errorCodes.FST_ERR_BAD_STATUS_CODE) { + reply.status(500).send({ ok: false }); + } else { + reply.send(error); + } +}); + +app.setNotFoundHandler((request, reply) => { + reply.code(404).type("text/html").send("Not Found"); +}); + +export default app; diff --git a/lib/auth.js b/lib/auth.js deleted file mode 100644 index 0a8d43d3..00000000 --- a/lib/auth.js +++ /dev/null @@ -1,51 +0,0 @@ -import fastifyPassport from "@fastify/passport"; -import { g } from "$lib/db"; -import { fail, getUser } from "$lib/utils"; -import config from "$config"; -import jwt from "passport-jwt"; - -export const admin = { - preValidation: fastifyPassport.authenticate( - "jwt", - { session: false }, - function (req, res, err, user, info) { - if (!user.admin) return res.code(401).send("unauthorized"); - req.user = user; - }, - ), -}; - -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)) fail("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) => { - let user = await getUser(payload.id); - next(null, user); - }, -); diff --git a/lib/auth.js.map b/lib/auth.js.map new file mode 100644 index 00000000..e539bafd --- /dev/null +++ b/lib/auth.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+DAAgD;AAEhD,oCAA2C;AAC3C,oDAA6B;AAC7B,8DAA+B;AAElB,QAAA,KAAK,GAAG;IACnB,aAAa,EAAE,kBAAe,CAAC,YAAY,CACzC,KAAK,EACL,EAAE,OAAO,EAAE,KAAK,EAAE,EAClB,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI;QACjC,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC3D,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC,CACF;CACF,CAAC;AAEW,QAAA,QAAQ,GAAG;IACtB,aAAa,EAAE,kBAAe,CAAC,YAAY,CACzC,KAAK,EACL,EAAE,OAAO,EAAE,KAAK,EAAE,EAClB,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI;QACjC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC,CACF;CACF,CAAC;AAEW,QAAA,IAAI,GAAG;IAClB,aAAa,EAAE,kBAAe,CAAC,YAAY,CAAC,KAAK,EAAE;QACjD,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,KAAK;KACf,CAAC;CACH,CAAC;AAEK,IAAM,UAAU,GAAG,iEAAO,EAAc;QAAZ,IAAI,UAAA,EAAE,IAAI,UAAA;;QAC3C,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC;YAAE,IAAA,YAAI,EAAC,aAAa,CAAC,CAAC;;;KACvE,CAAC;AAFW,QAAA,UAAU,cAErB;AAEW,QAAA,WAAW,GAAG,IAAI,sBAAG,CAAC,QAAQ,CACzC;IACE,cAAc,EAAE,sBAAG,CAAC,UAAU,CAAC,cAAc,CAAC;QAC5C,sBAAG,CAAC,UAAU,CAAC,2BAA2B,EAAE;QAC5C,UAAC,GAAG,IAAK,OAAA,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAxC,CAAwC;KAClD,CAAC;IACF,WAAW,EAAE,iBAAM,CAAC,GAAG;CACxB,EACD,UAAO,OAAO,EAAE,IAAI;;;;oBACP,qBAAM,IAAA,eAAO,EAAC,OAAO,CAAC,EAAE,CAAC,EAAA;;gBAAhC,IAAI,GAAG,SAAyB;gBACpC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;;;;KAClB,CACF,CAAC"} \ No newline at end of file diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 00000000..0a8d43d3 --- /dev/null +++ b/lib/auth.ts @@ -0,0 +1,51 @@ +import fastifyPassport from "@fastify/passport"; +import { g } from "$lib/db"; +import { fail, getUser } from "$lib/utils"; +import config from "$config"; +import jwt from "passport-jwt"; + +export const admin = { + preValidation: fastifyPassport.authenticate( + "jwt", + { session: false }, + function (req, res, err, user, info) { + if (!user.admin) return res.code(401).send("unauthorized"); + req.user = user; + }, + ), +}; + +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)) fail("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) => { + let user = await getUser(payload.id); + next(null, user); + }, +); diff --git a/lib/bitcoin.js b/lib/bitcoin.js deleted file mode 100644 index a811dc14..00000000 --- a/lib/bitcoin.js +++ /dev/null @@ -1,3 +0,0 @@ -import config from "$config"; -import rpc from "$lib/rpc"; -export default rpc(config.bitcoin); diff --git a/lib/bitcoin.js.map b/lib/bitcoin.js.map new file mode 100644 index 00000000..b236de0d --- /dev/null +++ b/lib/bitcoin.js.map @@ -0,0 +1 @@ +{"version":3,"file":"bitcoin.js","sourceRoot":"","sources":["bitcoin.ts"],"names":[],"mappings":";;;;;AAAA,oDAA6B;AAC7B,iDAA2B;AAC3B,kBAAe,IAAA,aAAG,EAAC,iBAAM,CAAC,OAAO,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/bitcoin.ts b/lib/bitcoin.ts new file mode 100644 index 00000000..a811dc14 --- /dev/null +++ b/lib/bitcoin.ts @@ -0,0 +1,3 @@ +import config from "$config"; +import rpc from "$lib/rpc"; +export default rpc(config.bitcoin); diff --git a/lib/countries.js b/lib/countries.js deleted file mode 100644 index 70f04c84..00000000 --- a/lib/countries.js +++ /dev/null @@ -1,248 +0,0 @@ -export default { - AF: "AFN", - AL: "ALL", - DZ: "DZD", - AD: "EUR", - AO: "AOA", - AI: "XCD", - AQ: "USD", - AR: "ARS", - AM: "AMD", - AW: "AWG", - AU: "AUD", - AT: "EUR", - AZ: "AZN", - BS: "BSD", - BH: "BHD", - BD: "BDT", - BB: "BBD", - BY: "BYN", - BE: "EUR", - BZ: "BZD", - BJ: "XOF", - BM: "BMD", - BT: "BTN", - BO: "BOB", - BW: "BWP", - BV: "NOK", - BR: "BRL", - IO: "USD", - VG: "USD", - BN: "BND", - BG: "BGN", - BF: "XOF", - BI: "BIF", - KH: "KHR", - CM: "XAF", - CA: "CAD", - IC: "EUR", - CV: "CVE", - KY: "KYD", - CF: "XAF", - TD: "XAF", - CL: "CLP", - CN: "CNY", - CX: "AUD", - CC: "AUD", - CO: "COP", - KM: "KMF", - CG: "XAF", - CD: "CDF", - CK: "NZD", - CR: "CRC", - CI: "XOF", - HR: "HRK", - CU: "CUP", - CW: "ANG", - CY: "EUR", - CZ: "CZK", - DK: "DKK", - DJ: "DJF", - DM: "XCD", - DO: "DOP", - TL: "USD", - EC: "USD", - EG: "EGP", - SV: "USD", - GQ: "XAF", - ER: "ERN", - EE: "EUR", - ET: "ETB", - FK: "FKP", - FO: "DKK", - FJ: "FJD", - FI: "EUR", - FR: "EUR", - GF: "EUR", - PF: "XPF", - TF: "EUR", - GA: "XAF", - GM: "GMD", - GE: "GEL", - DE: "EUR", - GH: "GHS", - GI: "GIP", - GR: "EUR", - GL: "DKK", - GD: "XCD", - GP: "EUR", - GU: "USD", - GT: "GTQ", - GG: "GBP", - GN: "GNF", - GW: "XOF", - GY: "GYD", - HT: "HTG", - HM: "AUD", - HN: "HNL", - HK: "HKD", - HU: "HUF", - IS: "ISK", - IN: "INR", - ID: "IDR", - IR: "IRR", - IQ: "IQD", - IE: "EUR", - IM: "GBP", - IL: "ILS", - IT: "EUR", - JM: "JMD", - JP: "JPY", - JE: "GBP", - JO: "JOD", - KZ: "KZT", - KE: "KES", - KI: "AUD", - XK: "EUR", - KW: "KWD", - KG: "KGS", - LA: "LAK", - LV: "EUR", - LB: "LBP", - LS: "LSL", - LR: "LRD", - LY: "LYD", - LI: "CHF", - LT: "EUR", - LU: "EUR", - MO: "MOP", - MK: "MKD", - MG: "MGA", - MW: "MWK", - MY: "MYR", - MV: "MVR", - ML: "XOF", - MT: "EUR", - MH: "USD", - MQ: "EUR", - MR: "MRO", - MU: "MUR", - YT: "EUR", - MX: "MXN", - FM: "USD", - MD: "MDL", - MC: "EUR", - MN: "MNT", - ME: "EUR", - MS: "XCD", - MA: "MAD", - MZ: "MZN", - MM: "MMK", - NA: "NAD", - NR: "AUD", - NP: "NPR", - NL: "EUR", - AN: "ANG", - NC: "XPF", - NZ: "NZD", - NI: "NIO", - NE: "XOF", - NG: "NGN", - NU: "NZD", - NF: "AUD", - KP: "KPW", - MP: "USD", - NO: "NOK", - OM: "OMR", - PK: "PKR", - PW: "USD", - PS: "ILS", - PA: "PAB", - PG: "PGK", - PY: "PYG", - PE: "PEN", - PH: "PHP", - PN: "NZD", - PL: "PLN", - PT: "EUR", - PR: "USD", - QA: "QAR", - RE: "EUR", - RO: "RON", - RU: "RUB", - RW: "RWF", - BL: "EUR", - SH: "SHP", - KN: "XCD", - LC: "XCD", - MF: "EUR", - PM: "EUR", - VC: "XCD", - WS: "WST", - SM: "EUR", - ST: "STN", - SA: "SAR", - SN: "XOF", - RS: "RSD", - SC: "SCR", - SL: "SLL", - SG: "SGD", - SX: "ANG", - SK: "EUR", - SI: "EUR", - SB: "SBD", - SO: "SOS", - ZA: "ZAR", - GS: "GBP", - KR: "KRW", - SS: "SSP", - ES: "EUR", - LK: "LKR", - SD: "SDG", - SR: "SRD", - SJ: "NOK", - SZ: "SZL", - SE: "SEK", - CH: "CHF", - SY: "SYP", - TW: "TWD", - TJ: "TJS", - TZ: "TZS", - TH: "THB", - TG: "XOF", - TK: "NZD", - TO: "TOP", - TT: "TTD", - TA: "SHP", - TN: "TND", - TR: "TRY", - TM: "TMT", - TC: "USD", - TV: "AUD", - UM: "USD", - VI: "USD", - UG: "UGX", - UA: "UAH", - AE: "AED", - GB: "GBP", - US: "USD", - UY: "UYU", - UZ: "UZS", - VU: "VUV", - VA: "EUR", - VE: "VES", - VN: "VND", - YE: "YER", - ZM: "ZMW", - ZW: "ZWL", -}; diff --git a/lib/countries.js.map b/lib/countries.js.map new file mode 100644 index 00000000..066e1d34 --- /dev/null +++ b/lib/countries.js.map @@ -0,0 +1 @@ +{"version":3,"file":"countries.js","sourceRoot":"","sources":["countries.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;CACV,CAAC"} \ No newline at end of file diff --git a/lib/countries.ts b/lib/countries.ts new file mode 100644 index 00000000..70f04c84 --- /dev/null +++ b/lib/countries.ts @@ -0,0 +1,248 @@ +export default { + AF: "AFN", + AL: "ALL", + DZ: "DZD", + AD: "EUR", + AO: "AOA", + AI: "XCD", + AQ: "USD", + AR: "ARS", + AM: "AMD", + AW: "AWG", + AU: "AUD", + AT: "EUR", + AZ: "AZN", + BS: "BSD", + BH: "BHD", + BD: "BDT", + BB: "BBD", + BY: "BYN", + BE: "EUR", + BZ: "BZD", + BJ: "XOF", + BM: "BMD", + BT: "BTN", + BO: "BOB", + BW: "BWP", + BV: "NOK", + BR: "BRL", + IO: "USD", + VG: "USD", + BN: "BND", + BG: "BGN", + BF: "XOF", + BI: "BIF", + KH: "KHR", + CM: "XAF", + CA: "CAD", + IC: "EUR", + CV: "CVE", + KY: "KYD", + CF: "XAF", + TD: "XAF", + CL: "CLP", + CN: "CNY", + CX: "AUD", + CC: "AUD", + CO: "COP", + KM: "KMF", + CG: "XAF", + CD: "CDF", + CK: "NZD", + CR: "CRC", + CI: "XOF", + HR: "HRK", + CU: "CUP", + CW: "ANG", + CY: "EUR", + CZ: "CZK", + DK: "DKK", + DJ: "DJF", + DM: "XCD", + DO: "DOP", + TL: "USD", + EC: "USD", + EG: "EGP", + SV: "USD", + GQ: "XAF", + ER: "ERN", + EE: "EUR", + ET: "ETB", + FK: "FKP", + FO: "DKK", + FJ: "FJD", + FI: "EUR", + FR: "EUR", + GF: "EUR", + PF: "XPF", + TF: "EUR", + GA: "XAF", + GM: "GMD", + GE: "GEL", + DE: "EUR", + GH: "GHS", + GI: "GIP", + GR: "EUR", + GL: "DKK", + GD: "XCD", + GP: "EUR", + GU: "USD", + GT: "GTQ", + GG: "GBP", + GN: "GNF", + GW: "XOF", + GY: "GYD", + HT: "HTG", + HM: "AUD", + HN: "HNL", + HK: "HKD", + HU: "HUF", + IS: "ISK", + IN: "INR", + ID: "IDR", + IR: "IRR", + IQ: "IQD", + IE: "EUR", + IM: "GBP", + IL: "ILS", + IT: "EUR", + JM: "JMD", + JP: "JPY", + JE: "GBP", + JO: "JOD", + KZ: "KZT", + KE: "KES", + KI: "AUD", + XK: "EUR", + KW: "KWD", + KG: "KGS", + LA: "LAK", + LV: "EUR", + LB: "LBP", + LS: "LSL", + LR: "LRD", + LY: "LYD", + LI: "CHF", + LT: "EUR", + LU: "EUR", + MO: "MOP", + MK: "MKD", + MG: "MGA", + MW: "MWK", + MY: "MYR", + MV: "MVR", + ML: "XOF", + MT: "EUR", + MH: "USD", + MQ: "EUR", + MR: "MRO", + MU: "MUR", + YT: "EUR", + MX: "MXN", + FM: "USD", + MD: "MDL", + MC: "EUR", + MN: "MNT", + ME: "EUR", + MS: "XCD", + MA: "MAD", + MZ: "MZN", + MM: "MMK", + NA: "NAD", + NR: "AUD", + NP: "NPR", + NL: "EUR", + AN: "ANG", + NC: "XPF", + NZ: "NZD", + NI: "NIO", + NE: "XOF", + NG: "NGN", + NU: "NZD", + NF: "AUD", + KP: "KPW", + MP: "USD", + NO: "NOK", + OM: "OMR", + PK: "PKR", + PW: "USD", + PS: "ILS", + PA: "PAB", + PG: "PGK", + PY: "PYG", + PE: "PEN", + PH: "PHP", + PN: "NZD", + PL: "PLN", + PT: "EUR", + PR: "USD", + QA: "QAR", + RE: "EUR", + RO: "RON", + RU: "RUB", + RW: "RWF", + BL: "EUR", + SH: "SHP", + KN: "XCD", + LC: "XCD", + MF: "EUR", + PM: "EUR", + VC: "XCD", + WS: "WST", + SM: "EUR", + ST: "STN", + SA: "SAR", + SN: "XOF", + RS: "RSD", + SC: "SCR", + SL: "SLL", + SG: "SGD", + SX: "ANG", + SK: "EUR", + SI: "EUR", + SB: "SBD", + SO: "SOS", + ZA: "ZAR", + GS: "GBP", + KR: "KRW", + SS: "SSP", + ES: "EUR", + LK: "LKR", + SD: "SDG", + SR: "SRD", + SJ: "NOK", + SZ: "SZL", + SE: "SEK", + CH: "CHF", + SY: "SYP", + TW: "TWD", + TJ: "TJS", + TZ: "TZS", + TH: "THB", + TG: "XOF", + TK: "NZD", + TO: "TOP", + TT: "TTD", + TA: "SHP", + TN: "TND", + TR: "TRY", + TM: "TMT", + TC: "USD", + TV: "AUD", + UM: "USD", + VI: "USD", + UG: "UGX", + UA: "UAH", + AE: "AED", + GB: "GBP", + US: "USD", + UY: "UYU", + UZ: "UZS", + VU: "VUV", + VA: "EUR", + VE: "VES", + VN: "VND", + YE: "YER", + ZM: "ZMW", + ZW: "ZWL", +}; diff --git a/lib/db.js b/lib/db.js deleted file mode 100644 index 4f113a1d..00000000 --- a/lib/db.js +++ /dev/null @@ -1,142 +0,0 @@ -import config from "$config"; -import { fail, wait, sleep } from "$lib/utils"; -import { createClient, defineScript } from "redis"; -import { warn, l, err } from "$lib/logging"; - -let SCRIPT = ` -local balanceKey = KEYS[1] -local creditKey = KEYS[2] -local amount = tonumber(ARGV[1]) -local tip = tonumber(ARGV[2]) -local fee = tonumber(ARGV[3]) -local ourfee = tonumber(ARGV[4]) - -local balance = tonumber(redis.call('get', balanceKey) or '0') -local credit = tonumber(redis.call('get', creditKey) or '0') - -local covered = math.min(credit, ourfee) -ourfee = ourfee - covered - -if balance < amount + tip + fee + ourfee then - return {err = 'Insufficient funds ⚡️' .. balance .. ' of ⚡️' .. amount + tip + fee + ourfee} -end - -redis.call('decrby', creditKey, tostring(math.floor(covered))) -redis.call('decrby', balanceKey, tostring(math.floor(amount + tip + fee + ourfee))) - -return ourfee -`; - -let debit = defineScript({ - NUMBER_OF_KEYS: 2, - SCRIPT, - transformArguments: (...args) => args.map((a) => a.toString()), -}); - -export let db = createClient({ - url: config.db, - scripts: { debit }, - socket: { - reconnectStrategy: (retries) => Math.min(retries * 50, 5000), - }, -}); - -export let archive = createClient({ - url: config.archive, - socket: { - reconnectStrategy: (retries) => Math.min(retries * 50, 5000), - }, -}); - -export let arc2 = createClient({ - url: config.arc2, - socket: { - reconnectStrategy: (retries) => Math.min(retries * 50, 5000), - }, -}); - -async function dbReconnect() { - try { - await db.connect(); - } catch (err) { - console.error("Failed to connect to Redis, retrying...", err); - setTimeout(dbReconnect, 5000); // Retry after 5 seconds - } -} - -async function archiveReconnect() { - try { - await archive.connect(); - } catch (err) { - console.error("Failed to connect to Redis, retrying...", err); - setTimeout(archiveReconnect, 5000); // Retry after 5 seconds - } -} - -dbReconnect(); -archiveReconnect(); - -db.on("error", (e) => { - if (e.message.startsWith("getaddr")) return; - err("Redis error", e.message); -}); - -db.on("end", () => { - warn("Redis connection ended"); -}); - -export default db; - -export let g = async (k) => { - let v = await db.get(k); - try { - return JSON.parse(v); - } catch (e) { - return v; - } -}; - -export let s = (k, v) => { - if (k === "user:null" || k === "user:undefined") fail("null user"); - db.set(k, JSON.stringify(v)); -}; - -export let ga = async (k) => { - let v = await archive.get(k); - try { - return JSON.parse(v); - } catch (e) { - return v; - } -}; - -export let sa = (k, v) => { - if (k === "user:null" || k === "user:undefined") { - warn("###### NULL USER #######"); - console.trace(); - } - archive.set(k, JSON.stringify(v)); -}; - -let retries = {}; -export let t = async (k, f) => { - try { - await db.watch(k); - await f(await db.get(k), db); - } catch (err) { - if (!err.message.includes("watch")) throw err; - - let r = retries[k] || 0; - retries[k] = r + 1; - - if (r < 10) { - await sleep(100); - await t(k, f); - } else { - delete retries[k]; - fail("unable to obtain lock"); - } - } - - delete retries[k]; -}; diff --git a/lib/db.js.map b/lib/db.js.map new file mode 100644 index 00000000..349459a4 --- /dev/null +++ b/lib/db.js.map @@ -0,0 +1 @@ +{"version":3,"file":"db.js","sourceRoot":"","sources":["db.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/db.ts b/lib/db.ts new file mode 100644 index 00000000..4f113a1d --- /dev/null +++ b/lib/db.ts @@ -0,0 +1,142 @@ +import config from "$config"; +import { fail, wait, sleep } from "$lib/utils"; +import { createClient, defineScript } from "redis"; +import { warn, l, err } from "$lib/logging"; + +let SCRIPT = ` +local balanceKey = KEYS[1] +local creditKey = KEYS[2] +local amount = tonumber(ARGV[1]) +local tip = tonumber(ARGV[2]) +local fee = tonumber(ARGV[3]) +local ourfee = tonumber(ARGV[4]) + +local balance = tonumber(redis.call('get', balanceKey) or '0') +local credit = tonumber(redis.call('get', creditKey) or '0') + +local covered = math.min(credit, ourfee) +ourfee = ourfee - covered + +if balance < amount + tip + fee + ourfee then + return {err = 'Insufficient funds ⚡️' .. balance .. ' of ⚡️' .. amount + tip + fee + ourfee} +end + +redis.call('decrby', creditKey, tostring(math.floor(covered))) +redis.call('decrby', balanceKey, tostring(math.floor(amount + tip + fee + ourfee))) + +return ourfee +`; + +let debit = defineScript({ + NUMBER_OF_KEYS: 2, + SCRIPT, + transformArguments: (...args) => args.map((a) => a.toString()), +}); + +export let db = createClient({ + url: config.db, + scripts: { debit }, + socket: { + reconnectStrategy: (retries) => Math.min(retries * 50, 5000), + }, +}); + +export let archive = createClient({ + url: config.archive, + socket: { + reconnectStrategy: (retries) => Math.min(retries * 50, 5000), + }, +}); + +export let arc2 = createClient({ + url: config.arc2, + socket: { + reconnectStrategy: (retries) => Math.min(retries * 50, 5000), + }, +}); + +async function dbReconnect() { + try { + await db.connect(); + } catch (err) { + console.error("Failed to connect to Redis, retrying...", err); + setTimeout(dbReconnect, 5000); // Retry after 5 seconds + } +} + +async function archiveReconnect() { + try { + await archive.connect(); + } catch (err) { + console.error("Failed to connect to Redis, retrying...", err); + setTimeout(archiveReconnect, 5000); // Retry after 5 seconds + } +} + +dbReconnect(); +archiveReconnect(); + +db.on("error", (e) => { + if (e.message.startsWith("getaddr")) return; + err("Redis error", e.message); +}); + +db.on("end", () => { + warn("Redis connection ended"); +}); + +export default db; + +export let g = async (k) => { + let v = await db.get(k); + try { + return JSON.parse(v); + } catch (e) { + return v; + } +}; + +export let s = (k, v) => { + if (k === "user:null" || k === "user:undefined") fail("null user"); + db.set(k, JSON.stringify(v)); +}; + +export let ga = async (k) => { + let v = await archive.get(k); + try { + return JSON.parse(v); + } catch (e) { + return v; + } +}; + +export let sa = (k, v) => { + if (k === "user:null" || k === "user:undefined") { + warn("###### NULL USER #######"); + console.trace(); + } + archive.set(k, JSON.stringify(v)); +}; + +let retries = {}; +export let t = async (k, f) => { + try { + await db.watch(k); + await f(await db.get(k), db); + } catch (err) { + if (!err.message.includes("watch")) throw err; + + let r = retries[k] || 0; + retries[k] = r + 1; + + if (r < 10) { + await sleep(100); + await t(k, f); + } else { + delete retries[k]; + fail("unable to obtain lock"); + } + } + + delete retries[k]; +}; diff --git a/lib/ecash.js b/lib/ecash.js deleted file mode 100644 index 4ce8912a..00000000 --- a/lib/ecash.js +++ /dev/null @@ -1,60 +0,0 @@ -import config from "$config"; -import { - CashuMint, - CashuWallet, - getDecodedToken, - getEncodedToken, - MintQuoteState, -} from "@cashu/cashu-ts"; -import { g, s } from "$lib/db"; -import ln from "$lib/ln"; -import { fail } from "$lib/utils"; - -let m = new CashuMint(config.mintUrl); -let w = new CashuWallet(m); - -let enc = (proofs) => - getEncodedToken({ - token: [{ mint: config.mintUrl, proofs }], - }); - -let dec = (token) => getDecodedToken(token).token[0]; - -let ext = async (mint) => { - let issuer = new CashuMint(mint); - let { pubkey: issuerPk } = await issuer.getInfo(); - let { pubkey: ourPk } = await m.getInfo(); - return issuerPk !== ourPk; -}; - -export async function claim(token) { - let { proofs: current } = dec(await g(`cash`)); - let { mint } = dec(token); - - if (await ext(mint)) fail("Unable to receive from other mints"); - - let rcvd = await w.receive(token); - - await s(`cash`, enc([...current, ...rcvd])); - return rcvd.reduce((a, b) => a + b.amount, 0); -} - -export async function mint(amount) { - let { proofs } = dec(await g(`cash`)); - let { send, returnChange } = await w.send(amount, proofs); - let rcvd = await w.receive(enc(send)); - await s(`cash`, enc(returnChange)); - return enc(rcvd); -} - -export async function check(token) { - let { mint, proofs } = dec(token); - let total = proofs.reduce((a, b) => a + b.amount, 0); - - let external = await ext(mint); - - let r = await w.checkProofsSpent(proofs); - let spent = r.reduce((a, b) => a + b.amount, 0); - - return { total, spent, mint, external }; -} diff --git a/lib/ecash.js.map b/lib/ecash.js.map new file mode 100644 index 00000000..d6cc9117 --- /dev/null +++ b/lib/ecash.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ecash.js","sourceRoot":"","sources":["ecash.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/ecash.ts b/lib/ecash.ts new file mode 100644 index 00000000..4ce8912a --- /dev/null +++ b/lib/ecash.ts @@ -0,0 +1,60 @@ +import config from "$config"; +import { + CashuMint, + CashuWallet, + getDecodedToken, + getEncodedToken, + MintQuoteState, +} from "@cashu/cashu-ts"; +import { g, s } from "$lib/db"; +import ln from "$lib/ln"; +import { fail } from "$lib/utils"; + +let m = new CashuMint(config.mintUrl); +let w = new CashuWallet(m); + +let enc = (proofs) => + getEncodedToken({ + token: [{ mint: config.mintUrl, proofs }], + }); + +let dec = (token) => getDecodedToken(token).token[0]; + +let ext = async (mint) => { + let issuer = new CashuMint(mint); + let { pubkey: issuerPk } = await issuer.getInfo(); + let { pubkey: ourPk } = await m.getInfo(); + return issuerPk !== ourPk; +}; + +export async function claim(token) { + let { proofs: current } = dec(await g(`cash`)); + let { mint } = dec(token); + + if (await ext(mint)) fail("Unable to receive from other mints"); + + let rcvd = await w.receive(token); + + await s(`cash`, enc([...current, ...rcvd])); + return rcvd.reduce((a, b) => a + b.amount, 0); +} + +export async function mint(amount) { + let { proofs } = dec(await g(`cash`)); + let { send, returnChange } = await w.send(amount, proofs); + let rcvd = await w.receive(enc(send)); + await s(`cash`, enc(returnChange)); + return enc(rcvd); +} + +export async function check(token) { + let { mint, proofs } = dec(token); + let total = proofs.reduce((a, b) => a + b.amount, 0); + + let external = await ext(mint); + + let r = await w.checkProofsSpent(proofs); + let spent = r.reduce((a, b) => a + b.amount, 0); + + return { total, spent, mint, external }; +} diff --git a/lib/invoices.js b/lib/invoices.js deleted file mode 100644 index 2f1225e7..00000000 --- a/lib/invoices.js +++ /dev/null @@ -1,128 +0,0 @@ -import crypto from "crypto"; -import store from "$lib/store"; -import { l } from "$lib/logging"; -import { emit } from "$lib/sockets"; -import { db, g, s } from "$lib/db"; -import { getUser, bip21, fail, SATS } from "$lib/utils"; -import { types } from "$lib/payments"; -import { v4 } from "uuid"; - -import lq from "$lib/liquid"; -import bc from "$lib/bitcoin"; -import ln from "$lib/ln"; - -export let generate = async ({ invoice, user, sender }) => { - let { - bolt11, - currency, - expiry, - fiat, - tip, - amount, - items = [], - rate, - request_id, - memo, - memoPrompt, - prompt, - type, - webhook, - secret, - } = invoice; - - amount = parseInt(amount || 0); - tip = parseInt(tip) || null; - - if (user) user = await getUser(user.username); - else if (sender) user = await getUser(sender.username); - if (!user) fail("user not provided"); - - let rates = await g('rates'); - if (!currency) currency = user.currency; - if (!rate) rate = rates[currency]; - if (fiat) amount = Math.round((SATS * fiat) / rate); - if (amount < 0) fail("invalid amount"); - if (tip < 0) fail("invalid tip"); - if (rate < 0) fail("invalid rate"); - if (memo && memo.length > 5000) fail("memo too long"); - - let id = v4(); - - let hash, text; - if (type === types.lightning) { - let r; - if (bolt11) { - let { id: nodeid } = await ln.getinfo(); - r = await ln.decode(bolt11); - if (r.payee !== nodeid) fail("invalid invoice"); - amount = Math.round(r.amount_msat / 1000); - r.bolt11 = bolt11; - } else { - r = await ln.invoice({ - amount_msat: amount ? `${amount + tip}sat` : "any", - label: id, - description: memo || "", - expiry: expiry || 60 * 60 * 24 * 30, - deschashonly: true, - cltv: 19, - }); - } - - hash = r.bolt11; - text = r.bolt11; - } else if (type === types.bitcoin) { - hash = await bc.getNewAddress(); - text = bip21(hash, invoice); - } else if (type === types.liquid) { - hash = await lq.getNewAddress(); - text = bip21(hash, invoice); - } else if (type === types.internal) { - hash = id; - } else { - fail("unrecognized type"); - } - - invoice = { - amount, - created: Date.now(), - currency, - hash, - id, - items, - memo, - rate, - pending: 0, - received: 0, - request_id, - memoPrompt, - prompt, - secret, - text, - tip, - type, - uid: user.id, - webhook, - }; - - if (type === "liquid") { - let { unconfidential } = await lq.getAddressInfo(hash); - await s(`invoice:${unconfidential}`, id); - } - - await s(`invoice:${hash}`, id); - await s(`invoice:${id}`, invoice); - await db.lPush(`${user.id}:invoices`, id); - - if (request_id) { - let request = await g(`request:${request_id}`); - if (request) { - let { invoice_id: prev } = request; - request.invoice_id = id; - await s(`request:${request_id}`, request); - - if (!prev) emit(request.requester_id, "invoice", invoice); - } - } - - return invoice; -}; diff --git a/lib/invoices.js.map b/lib/invoices.js.map new file mode 100644 index 00000000..e48d08fc --- /dev/null +++ b/lib/invoices.js.map @@ -0,0 +1 @@ +{"version":3,"file":"invoices.js","sourceRoot":"","sources":["invoices.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/invoices.ts b/lib/invoices.ts new file mode 100644 index 00000000..2f1225e7 --- /dev/null +++ b/lib/invoices.ts @@ -0,0 +1,128 @@ +import crypto from "crypto"; +import store from "$lib/store"; +import { l } from "$lib/logging"; +import { emit } from "$lib/sockets"; +import { db, g, s } from "$lib/db"; +import { getUser, bip21, fail, SATS } from "$lib/utils"; +import { types } from "$lib/payments"; +import { v4 } from "uuid"; + +import lq from "$lib/liquid"; +import bc from "$lib/bitcoin"; +import ln from "$lib/ln"; + +export let generate = async ({ invoice, user, sender }) => { + let { + bolt11, + currency, + expiry, + fiat, + tip, + amount, + items = [], + rate, + request_id, + memo, + memoPrompt, + prompt, + type, + webhook, + secret, + } = invoice; + + amount = parseInt(amount || 0); + tip = parseInt(tip) || null; + + if (user) user = await getUser(user.username); + else if (sender) user = await getUser(sender.username); + if (!user) fail("user not provided"); + + let rates = await g('rates'); + if (!currency) currency = user.currency; + if (!rate) rate = rates[currency]; + if (fiat) amount = Math.round((SATS * fiat) / rate); + if (amount < 0) fail("invalid amount"); + if (tip < 0) fail("invalid tip"); + if (rate < 0) fail("invalid rate"); + if (memo && memo.length > 5000) fail("memo too long"); + + let id = v4(); + + let hash, text; + if (type === types.lightning) { + let r; + if (bolt11) { + let { id: nodeid } = await ln.getinfo(); + r = await ln.decode(bolt11); + if (r.payee !== nodeid) fail("invalid invoice"); + amount = Math.round(r.amount_msat / 1000); + r.bolt11 = bolt11; + } else { + r = await ln.invoice({ + amount_msat: amount ? `${amount + tip}sat` : "any", + label: id, + description: memo || "", + expiry: expiry || 60 * 60 * 24 * 30, + deschashonly: true, + cltv: 19, + }); + } + + hash = r.bolt11; + text = r.bolt11; + } else if (type === types.bitcoin) { + hash = await bc.getNewAddress(); + text = bip21(hash, invoice); + } else if (type === types.liquid) { + hash = await lq.getNewAddress(); + text = bip21(hash, invoice); + } else if (type === types.internal) { + hash = id; + } else { + fail("unrecognized type"); + } + + invoice = { + amount, + created: Date.now(), + currency, + hash, + id, + items, + memo, + rate, + pending: 0, + received: 0, + request_id, + memoPrompt, + prompt, + secret, + text, + tip, + type, + uid: user.id, + webhook, + }; + + if (type === "liquid") { + let { unconfidential } = await lq.getAddressInfo(hash); + await s(`invoice:${unconfidential}`, id); + } + + await s(`invoice:${hash}`, id); + await s(`invoice:${id}`, invoice); + await db.lPush(`${user.id}:invoices`, id); + + if (request_id) { + let request = await g(`request:${request_id}`); + if (request) { + let { invoice_id: prev } = request; + request.invoice_id = id; + await s(`request:${request_id}`, request); + + if (!prev) emit(request.requester_id, "invoice", invoice); + } + } + + return invoice; +}; diff --git a/lib/lightning.js b/lib/lightning.js deleted file mode 100644 index 2d44581d..00000000 --- a/lib/lightning.js +++ /dev/null @@ -1,52 +0,0 @@ -import { handleZap } from "$lib/nostr"; -import config from "$config"; -import { notify } from "$lib/notifications"; -import { callWebhook } from "$lib/webhooks"; -import ln from "$lib/ln"; -import { l, err, warn } from "$lib/logging"; -import { g, s, t, db } from "$lib/db"; -import { credit, types } from "$lib/payments"; - -export async function listenForLightning() { - let inv = await ln.waitanyinvoice((await g("pay_index")) || 0); - let { - bolt11, - description, - pay_index, - status, - amount_received_msat, - payment_preimage: preimage, - } = inv; - - await s("pay_index", pay_index); - setTimeout(listenForLightning); - - let received = Math.round(amount_received_msat / 1000); - - try { - if (!preimage) return; - - let id = await g(`invoice:${bolt11}`); - let invoice = await g(`invoice:${id}`); - if (!invoice) return warn("received lightning with no invoice", bolt11); - - if (invoice && invoice.memo) { - try { - if (JSON.parse(description).kind === 9734) await handleZap(inv); - } catch (e) { - if (!e.message.startsWith("Unexpected")) - warn("failed to handle zap", e.message); - } - } - - let p = await g(`payment:${bolt11}`); - if (typeof p === "string") p = await g(`payment:${p}`); - - if (p) return warn("already processed", bolt11); - - await credit(bolt11, received, invoice.memo, preimage, types.lightning); - } catch (e) { - console.log(e); - err("problem receiving lightning payment", e.message); - } -} diff --git a/lib/lightning.js.map b/lib/lightning.js.map new file mode 100644 index 00000000..b98e19d7 --- /dev/null +++ b/lib/lightning.js.map @@ -0,0 +1 @@ +{"version":3,"file":"lightning.js","sourceRoot":"","sources":["lightning.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,gDA0CC;AAnDD,oCAAuC;AAIvC,+CAAyB;AACzB,wCAA4C;AAC5C,8BAAsC;AACtC,0CAA8C;AAE9C,SAAsB,kBAAkB;;;;;;oBACtB,KAAA,CAAA,KAAA,YAAE,CAAA,CAAC,cAAc,CAAA;oBAAE,qBAAM,IAAA,MAAC,EAAC,WAAW,CAAC,EAAA;wBAA7C,qBAAM,cAAkB,CAAC,SAAoB,CAAC,IAAI,CAAC,EAAC,EAAA;;oBAA1D,GAAG,GAAG,SAAoD;oBAE5D,MAAM,GAMJ,GAAG,OANC,EACN,WAAW,GAKT,GAAG,YALM,EACX,SAAS,GAIP,GAAG,UAJI,EACT,MAAM,GAGJ,GAAG,OAHC,EACN,oBAAoB,GAElB,GAAG,qBAFe,EACF,QAAQ,GACxB,GAAG,iBADqB,CACpB;oBAER,qBAAM,IAAA,MAAC,EAAC,WAAW,EAAE,SAAS,CAAC,EAAA;;oBAA/B,SAA+B,CAAC;oBAChC,UAAU,CAAC,kBAAkB,CAAC,CAAC;oBAE3B,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;;;;oBAGrD,IAAI,CAAC,QAAQ;wBAAE,sBAAO;oBAEb,qBAAM,IAAA,MAAC,EAAC,kBAAW,MAAM,CAAE,CAAC,EAAA;;oBAAjC,EAAE,GAAG,SAA4B;oBACvB,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;;oBAAlC,OAAO,GAAG,SAAwB;oBACtC,IAAI,CAAC,OAAO;wBAAE,sBAAO,IAAA,cAAI,EAAC,oCAAoC,EAAE,MAAM,CAAC,EAAC;yBAEpE,CAAA,OAAO,IAAI,OAAO,CAAC,IAAI,CAAA,EAAvB,yBAAuB;;;;yBAEnB,CAAA,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,KAAK,IAAI,CAAA,EAArC,wBAAqC;oBAAE,qBAAM,IAAA,iBAAS,EAAC,GAAG,CAAC,EAAA;;oBAApB,SAAoB,CAAC;;;;;oBAEhE,IAAI,CAAC,GAAC,CAAC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC;wBACrC,IAAA,cAAI,EAAC,sBAAsB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;yBAItC,qBAAM,IAAA,MAAC,EAAC,kBAAW,MAAM,CAAE,CAAC,EAAA;;oBAAhC,CAAC,GAAG,SAA4B;yBAChC,CAAA,OAAO,CAAC,KAAK,QAAQ,CAAA,EAArB,yBAAqB;oBAAM,qBAAM,IAAA,MAAC,EAAC,kBAAW,CAAC,CAAE,CAAC,EAAA;;oBAA3B,CAAC,GAAG,SAAuB,CAAC;;;oBAEvD,IAAI,CAAC;wBAAE,sBAAO,IAAA,cAAI,EAAC,mBAAmB,EAAE,MAAM,CAAC,EAAC;oBAEhD,qBAAM,IAAA,iBAAM,EAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,gBAAK,CAAC,SAAS,CAAC,EAAA;;oBAAvE,SAAuE,CAAC;;;;oBAExE,OAAO,CAAC,GAAG,CAAC,GAAC,CAAC,CAAC;oBACf,IAAA,aAAG,EAAC,qCAAqC,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;CAEzD"} \ No newline at end of file diff --git a/lib/lightning.ts b/lib/lightning.ts new file mode 100644 index 00000000..2d44581d --- /dev/null +++ b/lib/lightning.ts @@ -0,0 +1,52 @@ +import { handleZap } from "$lib/nostr"; +import config from "$config"; +import { notify } from "$lib/notifications"; +import { callWebhook } from "$lib/webhooks"; +import ln from "$lib/ln"; +import { l, err, warn } from "$lib/logging"; +import { g, s, t, db } from "$lib/db"; +import { credit, types } from "$lib/payments"; + +export async function listenForLightning() { + let inv = await ln.waitanyinvoice((await g("pay_index")) || 0); + let { + bolt11, + description, + pay_index, + status, + amount_received_msat, + payment_preimage: preimage, + } = inv; + + await s("pay_index", pay_index); + setTimeout(listenForLightning); + + let received = Math.round(amount_received_msat / 1000); + + try { + if (!preimage) return; + + let id = await g(`invoice:${bolt11}`); + let invoice = await g(`invoice:${id}`); + if (!invoice) return warn("received lightning with no invoice", bolt11); + + if (invoice && invoice.memo) { + try { + if (JSON.parse(description).kind === 9734) await handleZap(inv); + } catch (e) { + if (!e.message.startsWith("Unexpected")) + warn("failed to handle zap", e.message); + } + } + + let p = await g(`payment:${bolt11}`); + if (typeof p === "string") p = await g(`payment:${p}`); + + if (p) return warn("already processed", bolt11); + + await credit(bolt11, received, invoice.memo, preimage, types.lightning); + } catch (e) { + console.log(e); + err("problem receiving lightning payment", e.message); + } +} diff --git a/lib/liquid.js b/lib/liquid.js deleted file mode 100644 index ec8ec7cb..00000000 --- a/lib/liquid.js +++ /dev/null @@ -1,3 +0,0 @@ -import config from "$config"; -import rpc from "$lib/rpc"; -export default rpc(config.liquid); diff --git a/lib/liquid.js.map b/lib/liquid.js.map new file mode 100644 index 00000000..2c399781 --- /dev/null +++ b/lib/liquid.js.map @@ -0,0 +1 @@ +{"version":3,"file":"liquid.js","sourceRoot":"","sources":["liquid.ts"],"names":[],"mappings":";;;;;AAAA,oDAA6B;AAC7B,iDAA2B;AAC3B,kBAAe,IAAA,aAAG,EAAC,iBAAM,CAAC,MAAM,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/liquid.ts b/lib/liquid.ts new file mode 100644 index 00000000..49bd838d --- /dev/null +++ b/lib/liquid.ts @@ -0,0 +1,4 @@ +import config from "$config"; +import rpc from "$lib/rpc"; +console.log("LIQ", config.liquid); +export default rpc(config.liquid); diff --git a/lib/ln.js b/lib/ln.js deleted file mode 100644 index c4445b26..00000000 --- a/lib/ln.js +++ /dev/null @@ -1,7 +0,0 @@ -import config from "$config"; -let ln; - -let { LightningClient } = (await import("clightning-client")).default; -ln = new LightningClient(config.lightning); - -export default ln; diff --git a/lib/ln.js.map b/lib/ln.js.map new file mode 100644 index 00000000..418dc9e2 --- /dev/null +++ b/lib/ln.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ln.js","sourceRoot":"","sources":["ln.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAA6B;AAC7B,IAAI,EAAE,CAAC;AAED,IAAA,eAAe,GAAK,CAAC,uEAAa,mBAAmB,MAAC,CAAC,CAAC,OAAO,gBAAhD,CAAiD;AACtE,EAAE,GAAG,IAAI,eAAe,CAAC,iBAAM,CAAC,SAAS,CAAC,CAAC;AAE3C,kBAAe,EAAE,CAAC"} \ No newline at end of file diff --git a/lib/ln.ts b/lib/ln.ts new file mode 100644 index 00000000..c4445b26 --- /dev/null +++ b/lib/ln.ts @@ -0,0 +1,7 @@ +import config from "$config"; +let ln; + +let { LightningClient } = (await import("clightning-client")).default; +ln = new LightningClient(config.lightning); + +export default ln; diff --git a/lib/locales/index.js b/lib/locales/index.js deleted file mode 100644 index 3416a31b..00000000 --- a/lib/locales/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import fs from "fs"; -export default "de,en,fa,ja,ru,el,es,fr,pt,zh".split(",").reduce((a, l) => { - a[l] = JSON.parse(fs.readFileSync(`lib/locales/${l}.json`)); - return a; -}, {}); diff --git a/lib/locales/index.js.map b/lib/locales/index.js.map new file mode 100644 index 00000000..3fe687da --- /dev/null +++ b/lib/locales/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;;;AAAA,0CAAoB;AACpB,kBAAe,+BAA+B,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,UAAC,CAAC,EAAE,CAAC;IAChE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAE,CAAC,YAAY,CAAC,sBAAe,CAAC,UAAO,CAAC,CAAC,CAAC;IAC9D,OAAO,CAAC,CAAC;AACb,CAAC,EAAE,EAAE,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/locales/index.ts b/lib/locales/index.ts new file mode 100644 index 00000000..3416a31b --- /dev/null +++ b/lib/locales/index.ts @@ -0,0 +1,5 @@ +import fs from "fs"; +export default "de,en,fa,ja,ru,el,es,fr,pt,zh".split(",").reduce((a, l) => { + a[l] = JSON.parse(fs.readFileSync(`lib/locales/${l}.json`)); + return a; +}, {}); diff --git a/lib/locations.js b/lib/locations.js deleted file mode 100644 index 39212d05..00000000 --- a/lib/locations.js +++ /dev/null @@ -1,40 +0,0 @@ -import fs from "fs"; -import { err } from "$lib/logging"; -import got from "got"; -import { g, s } from "$lib/db"; -import { fields, pick } from "$lib/utils"; - -export let getLocations = async () => { - try { - let locations = await got( - "https://api.btcmap.org/v2/elements?updated_since=2022-09-19T00:00:00Z", - ).json(); - - locations = locations.filter( - (l) => - l["osm_json"].tags && l["osm_json"].tags["payment:coinos"] === "yes", - ); - - locations.map((l) => { - let { bounds, lat, lon } = l["osm_json"]; - - l["osm_json"].lat = lat || (bounds.minlat + bounds.maxlat) / 2; - l["osm_json"].lon = lon || (bounds.minlon + bounds.maxlon) / 2; - }); - - for await (let l of locations) { - let username = l.tags["payment:coinos"]; - if (username) { - let uid = await g(`user:${username}`); - let user = await g(`user:${uid}`); - if (user) l["osm_json"].tags.user = pick(user, fields); - } - } - - await s("locations", locations); - } catch (e) { - err("problem fetching locations", e); - } - - setTimeout(getLocations, 60000); -}; diff --git a/lib/locations.js.map b/lib/locations.js.map new file mode 100644 index 00000000..c41e5a3c --- /dev/null +++ b/lib/locations.js.map @@ -0,0 +1 @@ +{"version":3,"file":"locations.js","sourceRoot":"","sources":["locations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,wCAAmC;AACnC,4CAAsB;AACtB,8BAA+B;AAC/B,oCAA0C;AAEnC,IAAI,YAAY,GAAG;;;;;;;gBAEN,qBAAM,IAAA,aAAG,EACvB,uEAAuE,CACxE,CAAC,IAAI,EAAE,EAAA;;gBAFJ,SAAS,GAAG,SAER;gBAER,SAAS,GAAG,SAAS,CAAC,MAAM,CAC1B,UAAC,CAAC;oBACA,OAAA,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,KAAK;gBAApE,CAAoE,CACvE,CAAC;gBAEF,SAAS,CAAC,GAAG,CAAC,UAAC,CAAC;;oBACR,IAAA,MAAM,IAAR,KAAuB,CAAC,CAAC,UAAU,CAAC,YAA5B,EAAE,GAAG,SAAA,EAAE,GAAG,SAAA,CAAmB;oBAEzC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBAC/D,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjE,CAAC,CAAC,CAAC;;;;2BAEiB,cAAA,cAAA,SAAS,CAAA;;;;;gBAAT,yBAAS;gBAAT,WAAS;gBAAd,CAAC,KAAA,CAAA;gBACV,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;qBACpC,QAAQ,EAAR,wBAAQ;gBACA,qBAAM,IAAA,MAAC,EAAC,eAAQ,QAAQ,CAAE,CAAC,EAAA;;gBAAjC,GAAG,GAAG,SAA2B;gBAC1B,qBAAM,IAAA,MAAC,EAAC,eAAQ,GAAG,CAAE,CAAC,EAAA;;gBAA7B,IAAI,GAAG,SAAsB;gBACjC,IAAI,IAAI;oBAAE,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,IAAA,YAAI,EAAC,IAAI,EAAE,cAAM,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;qBAI3D,qBAAM,IAAA,MAAC,EAAC,WAAW,EAAE,SAAS,CAAC,EAAA;;gBAA/B,SAA+B,CAAC;;;;gBAEhC,IAAA,aAAG,EAAC,4BAA4B,EAAE,GAAC,CAAC,CAAC;;;gBAGvC,UAAU,CAAC,oBAAY,EAAE,KAAK,CAAC,CAAC;;;;KACjC,CAAC;AAjCS,QAAA,YAAY,gBAiCrB"} \ No newline at end of file diff --git a/lib/locations.ts b/lib/locations.ts new file mode 100644 index 00000000..39212d05 --- /dev/null +++ b/lib/locations.ts @@ -0,0 +1,40 @@ +import fs from "fs"; +import { err } from "$lib/logging"; +import got from "got"; +import { g, s } from "$lib/db"; +import { fields, pick } from "$lib/utils"; + +export let getLocations = async () => { + try { + let locations = await got( + "https://api.btcmap.org/v2/elements?updated_since=2022-09-19T00:00:00Z", + ).json(); + + locations = locations.filter( + (l) => + l["osm_json"].tags && l["osm_json"].tags["payment:coinos"] === "yes", + ); + + locations.map((l) => { + let { bounds, lat, lon } = l["osm_json"]; + + l["osm_json"].lat = lat || (bounds.minlat + bounds.maxlat) / 2; + l["osm_json"].lon = lon || (bounds.minlon + bounds.maxlon) / 2; + }); + + for await (let l of locations) { + let username = l.tags["payment:coinos"]; + if (username) { + let uid = await g(`user:${username}`); + let user = await g(`user:${uid}`); + if (user) l["osm_json"].tags.user = pick(user, fields); + } + } + + await s("locations", locations); + } catch (e) { + err("problem fetching locations", e); + } + + setTimeout(getLocations, 60000); +}; diff --git a/lib/logging.js b/lib/logging.js deleted file mode 100644 index 4913839a..00000000 --- a/lib/logging.js +++ /dev/null @@ -1,12 +0,0 @@ -import pino from "pino"; - -export let l = (...msgs) => pino().info(msgs.join(" ")); -export let warn = (...msgs) => pino().warn(msgs.join(" ")); -export let err = (...msgs) => pino().error(msgs.join(" ")); - -export let line = () => { - let stack = new Error().stack; - let stackLine = stack.split("\n")[1]; - let match = stackLine.match(/at\s+(.*):(\d+):(\d+)/); - return `${match[1]}:${match[2]}`; -}; diff --git a/lib/logging.js.map b/lib/logging.js.map new file mode 100644 index 00000000..d3ddf096 --- /dev/null +++ b/lib/logging.js.map @@ -0,0 +1 @@ +{"version":3,"file":"logging.js","sourceRoot":"","sources":["logging.ts"],"names":[],"mappings":";;;;;;AAAA,8CAAwB;AAEjB,IAAI,CAAC,GAAG;IAAC,cAAO;SAAP,UAAO,EAAP,qBAAO,EAAP,IAAO;QAAP,yBAAO;;IAAK,OAAA,IAAA,cAAI,GAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAA3B,CAA2B,CAAC;AAA7C,QAAA,CAAC,KAA4C;AACjD,IAAI,IAAI,GAAG;IAAC,cAAO;SAAP,UAAO,EAAP,qBAAO,EAAP,IAAO;QAAP,yBAAO;;IAAK,OAAA,IAAA,cAAI,GAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAA3B,CAA2B,CAAC;AAAhD,QAAA,IAAI,QAA4C;AACpD,IAAI,GAAG,GAAG;IAAC,cAAO;SAAP,UAAO,EAAP,qBAAO,EAAP,IAAO;QAAP,yBAAO;;IAAK,OAAA,IAAA,cAAI,GAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAA5B,CAA4B,CAAC;AAAhD,QAAA,GAAG,OAA6C;AAEpD,IAAI,IAAI,GAAG;IAChB,IAAI,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,KAAK,CAAC;IAC9B,IAAI,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,IAAI,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACrD,OAAO,UAAG,KAAK,CAAC,CAAC,CAAC,cAAI,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC;AACnC,CAAC,CAAC;AALS,QAAA,IAAI,QAKb"} \ No newline at end of file diff --git a/lib/logging.ts b/lib/logging.ts new file mode 100644 index 00000000..4913839a --- /dev/null +++ b/lib/logging.ts @@ -0,0 +1,12 @@ +import pino from "pino"; + +export let l = (...msgs) => pino().info(msgs.join(" ")); +export let warn = (...msgs) => pino().warn(msgs.join(" ")); +export let err = (...msgs) => pino().error(msgs.join(" ")); + +export let line = () => { + let stack = new Error().stack; + let stackLine = stack.split("\n")[1]; + let match = stackLine.match(/at\s+(.*):(\d+):(\d+)/); + return `${match[1]}:${match[2]}`; +}; diff --git a/lib/mail.js b/lib/mail.js deleted file mode 100644 index 16b3dbbf..00000000 --- a/lib/mail.js +++ /dev/null @@ -1,44 +0,0 @@ -import { l, err, warn } from "$lib/logging"; -import config from "$config"; -import { SESClient } from "@aws-sdk/client-ses"; -import { SendEmailCommand } from "@aws-sdk/client-ses"; -import handlebars from "handlebars"; -import fs from "fs"; -import path from "path"; - -let Charset = "UTF-8"; - -export let templates = { - verifyEmail: "templates/payments/verify.html", - paymentReceived: "templates/payments/received.html", - passwordReset: "templates/payments/reset.html", -}; - -export let mail = async (user, subject, template, params) => { - try { - l("sending mail", user.username, subject); - if (!user.email) return; - - let source = fs.readFileSync(template, "utf8"); - let html = handlebars.compile(source)(params); - - let client = new SESClient({ region: "us-east-2" }); - - await client.send( - new SendEmailCommand({ - Destination: { - ToAddresses: [user.email], - }, - Message: { - Body: { - Html: { Charset, Data: html }, - }, - Subject: { Charset, Data: subject }, - }, - Source: `"Coinos " <${config.support}>`, - }), - ); - } catch (e) { - err("failed to send email", e.message); - } -}; diff --git a/lib/mail.js.map b/lib/mail.js.map new file mode 100644 index 00000000..afc37431 --- /dev/null +++ b/lib/mail.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mail.js","sourceRoot":"","sources":["mail.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wCAA4C;AAC5C,oDAA6B;AAC7B,kDAAgD;AAChD,kDAAuD;AACvD,0DAAoC;AACpC,0CAAoB;AAGpB,IAAI,OAAO,GAAG,OAAO,CAAC;AAEX,QAAA,SAAS,GAAG;IACrB,WAAW,EAAE,gCAAgC;IAC7C,eAAe,EAAE,kCAAkC;IACnD,aAAa,EAAE,+BAA+B;CAC/C,CAAC;AAEK,IAAI,IAAI,GAAG,UAAO,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM;;;;;;gBAEpD,IAAA,WAAC,EAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAC1C,IAAI,CAAC,IAAI,CAAC,KAAK;oBAAE,sBAAO;gBAEpB,MAAM,GAAG,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAC3C,IAAI,GAAG,oBAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;gBAE1C,MAAM,GAAG,IAAI,sBAAS,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;gBAEpD,qBAAM,MAAM,CAAC,IAAI,CACf,IAAI,6BAAgB,CAAC;wBACnB,WAAW,EAAE;4BACX,WAAW,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;yBAC1B;wBACD,OAAO,EAAE;4BACP,IAAI,EAAE;gCACJ,IAAI,EAAE,EAAE,OAAO,SAAA,EAAE,IAAI,EAAE,IAAI,EAAE;6BAC9B;4BACD,OAAO,EAAE,EAAE,OAAO,SAAA,EAAE,IAAI,EAAE,OAAO,EAAE;yBACpC;wBACD,MAAM,EAAE,uBAAc,iBAAM,CAAC,OAAO,MAAG;qBACxC,CAAC,CACH,EAAA;;gBAbD,SAaC,CAAC;;;;gBAEF,IAAA,aAAG,EAAC,sBAAsB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;KAE1C,CAAC;AA3BS,QAAA,IAAI,QA2Bb"} \ No newline at end of file diff --git a/lib/mail.ts b/lib/mail.ts new file mode 100644 index 00000000..16b3dbbf --- /dev/null +++ b/lib/mail.ts @@ -0,0 +1,44 @@ +import { l, err, warn } from "$lib/logging"; +import config from "$config"; +import { SESClient } from "@aws-sdk/client-ses"; +import { SendEmailCommand } from "@aws-sdk/client-ses"; +import handlebars from "handlebars"; +import fs from "fs"; +import path from "path"; + +let Charset = "UTF-8"; + +export let templates = { + verifyEmail: "templates/payments/verify.html", + paymentReceived: "templates/payments/received.html", + passwordReset: "templates/payments/reset.html", +}; + +export let mail = async (user, subject, template, params) => { + try { + l("sending mail", user.username, subject); + if (!user.email) return; + + let source = fs.readFileSync(template, "utf8"); + let html = handlebars.compile(source)(params); + + let client = new SESClient({ region: "us-east-2" }); + + await client.send( + new SendEmailCommand({ + Destination: { + ToAddresses: [user.email], + }, + Message: { + Body: { + Html: { Charset, Data: html }, + }, + Subject: { Charset, Data: subject }, + }, + Source: `"Coinos " <${config.support}>`, + }), + ); + } catch (e) { + err("failed to send email", e.message); + } +}; diff --git a/lib/migrate.js b/lib/migrate.js deleted file mode 100644 index cee5e946..00000000 --- a/lib/migrate.js +++ /dev/null @@ -1,134 +0,0 @@ -import { err, warn, l, line } from "$lib/logging"; -import { types } from "$lib/payments"; -import { archive, arc2, db, g, ga, s } from "$lib/db"; -import { wait } from "$lib/utils"; - -let queued = {}; -let migrating = {}; - -async function migrate(id) { - try { - delete queued[id]; - if (id) id = id.replace(/\s/g, "").toLowerCase(); - - let user = await g(`user:${id}`); - if (typeof user === "string") user = await g(`user:${user}`); - - if (user) return user; - - user = await ga(`user:${id}`); - - if (typeof user === "string") { - if (await g(`user:${user}`)) { - warn("user already migrated", id); - s(`user:${id}`, user); - } - - id = user; - user = await ga(`user:${id}`); - } - - if (user) { - if (migrating[id]) return; - migrating[id] = true; - - let { username } = user; - username = username.replace(/\s/g, "").toLowerCase(); - l("migrating", username); - - try { - let multi = await db - .multi() - .set(`user:${username}`, id) - .set(`user:${user.pubkey}`, id) - .set(`user:${id}`, JSON.stringify(user)); - - await multi.exec(); - - await archive.del(`user:${username}`); - await archive.del(`user:${user.pubkey}`); - await archive.del(`user:${id}`); - - await s(`${id}:contacts`, (await ga(`${id}:contacts`)) || []); - await archive.del(`${id}:contacts`); - - await s( - `credit:bitcoin:${id}`, - (await ga(`credit:bitcoin:${id}`)) || 0, - ); - await archive.del(`credit:bitcoin:${id}`); - - await s( - `credit:lightning:${id}`, - (await ga(`credit:lightning:${id}`)) || 0, - ); - await archive.del(`credit:lightning:${id}`); - - await s(`${id}:lastlen`, (await ga(`${id}:lastlen`)) || 0); - await archive.del(`${id}:lastlen`); - await s(`${id}:cindex`, (await ga(`${id}:cindex`)) || 0); - await archive.del(`${id}:cindex`); - await s(`balance:${id}`, (await ga(`balance:${id}`)) || 0); - await archive.del(`balance:${id}`); - await s(`pending:${id}`, (await ga(`pending:${id}`)) || 0); - await archive.del(`pending:${id}`); - - let pid; - while ((pid = await archive.rPop(`${id}:payments`))) { - let p = await ga(`payment:${pid}`); - let pos = await db.lPos(`${id}:payments`, pid); - if (!p || pos) continue; - if (p.type === types.internal && p.ref) { - let recipient = await g(`user:${p.ref}`); - if (!recipient) queued[p.ref] = true; - } - - await db.lPush(`${id}:payments`, pid); - await s(`payment:${pid}`, p); - - await archive.del(`payment:${pid}`); - } - await archive.del(`${id}:payments`); - - let iid; - while ((iid = await archive.rPop(`${id}:invoices`))) { - let inv = await ga(`invoice:${iid}`); - let pos = await db.lPos(`${id}:invoices`, iid); - if (!inv || pos) continue; - await db.lPush(`${id}:invoices`, iid); - await s(`invoice:${iid}`, inv); - - await archive.del(`invoice:${iid}`); - } - await archive.del(`${id}:invoices`); - - l("done migrating", username); - } catch (e) { - warn("failed to migrate", username, e.message, line()); - } - - delete migrating[id]; - return user; - } - } catch (e) { - delete migrating[id]; - throw e; - } -} - -let keepMigrating = async () => { - try { - if (Object.keys(queued).length) { - let next = Object.keys(queued)[0]; - await migrate(next); - } - } catch (e) { - err("problem migrating", e.message); - } - - setTimeout(keepMigrating, 1000); -}; - -keepMigrating(); - -export default migrate; diff --git a/lib/migrate.js.map b/lib/migrate.js.map new file mode 100644 index 00000000..7e905ae3 --- /dev/null +++ b/lib/migrate.js.map @@ -0,0 +1 @@ +{"version":3,"file":"migrate.js","sourceRoot":"","sources":["migrate.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/migrate.ts b/lib/migrate.ts new file mode 100644 index 00000000..cee5e946 --- /dev/null +++ b/lib/migrate.ts @@ -0,0 +1,134 @@ +import { err, warn, l, line } from "$lib/logging"; +import { types } from "$lib/payments"; +import { archive, arc2, db, g, ga, s } from "$lib/db"; +import { wait } from "$lib/utils"; + +let queued = {}; +let migrating = {}; + +async function migrate(id) { + try { + delete queued[id]; + if (id) id = id.replace(/\s/g, "").toLowerCase(); + + let user = await g(`user:${id}`); + if (typeof user === "string") user = await g(`user:${user}`); + + if (user) return user; + + user = await ga(`user:${id}`); + + if (typeof user === "string") { + if (await g(`user:${user}`)) { + warn("user already migrated", id); + s(`user:${id}`, user); + } + + id = user; + user = await ga(`user:${id}`); + } + + if (user) { + if (migrating[id]) return; + migrating[id] = true; + + let { username } = user; + username = username.replace(/\s/g, "").toLowerCase(); + l("migrating", username); + + try { + let multi = await db + .multi() + .set(`user:${username}`, id) + .set(`user:${user.pubkey}`, id) + .set(`user:${id}`, JSON.stringify(user)); + + await multi.exec(); + + await archive.del(`user:${username}`); + await archive.del(`user:${user.pubkey}`); + await archive.del(`user:${id}`); + + await s(`${id}:contacts`, (await ga(`${id}:contacts`)) || []); + await archive.del(`${id}:contacts`); + + await s( + `credit:bitcoin:${id}`, + (await ga(`credit:bitcoin:${id}`)) || 0, + ); + await archive.del(`credit:bitcoin:${id}`); + + await s( + `credit:lightning:${id}`, + (await ga(`credit:lightning:${id}`)) || 0, + ); + await archive.del(`credit:lightning:${id}`); + + await s(`${id}:lastlen`, (await ga(`${id}:lastlen`)) || 0); + await archive.del(`${id}:lastlen`); + await s(`${id}:cindex`, (await ga(`${id}:cindex`)) || 0); + await archive.del(`${id}:cindex`); + await s(`balance:${id}`, (await ga(`balance:${id}`)) || 0); + await archive.del(`balance:${id}`); + await s(`pending:${id}`, (await ga(`pending:${id}`)) || 0); + await archive.del(`pending:${id}`); + + let pid; + while ((pid = await archive.rPop(`${id}:payments`))) { + let p = await ga(`payment:${pid}`); + let pos = await db.lPos(`${id}:payments`, pid); + if (!p || pos) continue; + if (p.type === types.internal && p.ref) { + let recipient = await g(`user:${p.ref}`); + if (!recipient) queued[p.ref] = true; + } + + await db.lPush(`${id}:payments`, pid); + await s(`payment:${pid}`, p); + + await archive.del(`payment:${pid}`); + } + await archive.del(`${id}:payments`); + + let iid; + while ((iid = await archive.rPop(`${id}:invoices`))) { + let inv = await ga(`invoice:${iid}`); + let pos = await db.lPos(`${id}:invoices`, iid); + if (!inv || pos) continue; + await db.lPush(`${id}:invoices`, iid); + await s(`invoice:${iid}`, inv); + + await archive.del(`invoice:${iid}`); + } + await archive.del(`${id}:invoices`); + + l("done migrating", username); + } catch (e) { + warn("failed to migrate", username, e.message, line()); + } + + delete migrating[id]; + return user; + } + } catch (e) { + delete migrating[id]; + throw e; + } +} + +let keepMigrating = async () => { + try { + if (Object.keys(queued).length) { + let next = Object.keys(queued)[0]; + await migrate(next); + } + } catch (e) { + err("problem migrating", e.message); + } + + setTimeout(keepMigrating, 1000); +}; + +keepMigrating(); + +export default migrate; diff --git a/lib/mqtt.js b/lib/mqtt.js deleted file mode 100644 index 636dc3e1..00000000 --- a/lib/mqtt.js +++ /dev/null @@ -1,5 +0,0 @@ -import mqtt from "mqtt"; -import config from "$config"; - -export let mqtt1 = mqtt.connect("mqtt://ln.coinos.io", config.mqtt1); -export let mqtt2 = mqtt.connect("mqtt://mqtt.coinos.io", config.mqtt2); diff --git a/lib/mqtt.js.map b/lib/mqtt.js.map new file mode 100644 index 00000000..d4a56ea2 --- /dev/null +++ b/lib/mqtt.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mqtt.js","sourceRoot":"","sources":["mqtt.ts"],"names":[],"mappings":";;;;;;AAAA,8CAAwB;AACxB,oDAA6B;AAElB,QAAA,KAAK,GAAG,cAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,iBAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,QAAA,KAAK,GAAG,cAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,iBAAM,CAAC,KAAK,CAAC,CAAC"} \ No newline at end of file diff --git a/lib/mqtt.ts b/lib/mqtt.ts new file mode 100644 index 00000000..636dc3e1 --- /dev/null +++ b/lib/mqtt.ts @@ -0,0 +1,5 @@ +import mqtt from "mqtt"; +import config from "$config"; + +export let mqtt1 = mqtt.connect("mqtt://ln.coinos.io", config.mqtt1); +export let mqtt2 = mqtt.connect("mqtt://mqtt.coinos.io", config.mqtt2); diff --git a/lib/nostr.js b/lib/nostr.js deleted file mode 100644 index ccfb4e54..00000000 --- a/lib/nostr.js +++ /dev/null @@ -1,280 +0,0 @@ -import config from "$config"; -import { l } from "$lib/logging"; -import { Relay, RelayPool, calculateId, signId, getPublicKey } from "nostr"; -import { broadcast, emit } from "$lib/sockets"; -import store from "$lib/store"; -import { g, s, db } from "$lib/db"; -import { fail, nada, wait } from "$lib/utils"; -import { nip19 } from "nostr-tools"; - -export const COINOS_PUBKEY = getPublicKey(nip19.decode(config.nostrKey).data); -export let coinos; -export let pool; - -export let fillPool = () => { - try { - pool = RelayPool(config.relays); - pool.on("open", (relay) => { - if (relay.url.includes(config.nostr)) coinos = relay; - relay.subscribe("live", { limit: 1 }); - }); - - pool.on("event", async (relay, sub, ev) => { - if (!coinos) return; - if (timeouts[sub]) timeouts[sub].extend(ev); - try { - let content; - try { - content = JSON.parse(ev.content); - } catch (e) { - content = ev.content; - } - - if (sub === "live") { - let { pubkey } = ev; - - if (Math.abs(Math.floor(Date.now() / 1000) - ev.created_at) > 7200) - return; - - if (seen.includes(ev.id)) return; - seen.push(ev.id); - seen.length > 1000 && seen.shift(); - - if (coinos && ev.kind < 5) { - // coinos.send(["EVENT", ev]); - ev.user = await getUser(pubkey); - // broadcast("event", ev); - - if (ev.kind === 4) { - let uid = await g(`user:${ev.tags[0][1]}`); - if (uid) ev.recipient = await g(`user:${uid}`); - ev.author = ev.user; - emit(uid, "event", ev); - } - } - } else if (sub.includes("profile")) { - let { pubkey } = ev; - let user = await getUser(pubkey); - - if (user.updated > ev.created_at) return; - - if (content.name && user.username === pubkey.substr(0, 6)) - user.username = content.name; - - delete content.name; - - user = { - ...user, - ...content, - updated: ev.created_at, - }; - - await s(`user:${pubkey}`, JSON.stringify(user)); - } else if (sub.includes("messages")) { - let pubkey = sub.split(":")[0]; - await db.sAdd(`${pubkey}:messages`, ev.id); - await s(`ev:${ev.id}`, ev); - } else if (sub.includes("follows")) { - let pubkey = sub.split(":")[0]; - await s(`${pubkey}:follows`, ev.tags); - for (let f of ev.tags) { - let [_, followPubkey] = f; - await db.sAdd(`${followPubkey}:followers`, pubkey); - } - coinos.send(ev); - } else if (sub.includes("followers")) { - let followed = sub.split(":")[0]; - let { pubkey } = ev; - await db.sAdd(`${followed}:followers`, pubkey); - coinos.send(ev); - } else if (sub.includes("notes")) { - let pubkey = sub.split(":")[0]; - await db.sAdd(pubkey, ev.id); - await s(`ev:${ev.id}`, ev); - } - } catch (e) { - console.log(e); - } - }); - } catch(e) { - console.log("POOL", e) - } -}; - -let now = () => Math.round(Date.now() / 1000); - -let timeouts = {}; -export let q = async ( - sub, - query, - { timeout = 20000, since = 3600, eager = 200 }, -) => - new Promise(async (r, j) => { - let start = Date.now(); - let seen = []; - let rejected; - // query.since = await g(`since:${sub}`); - // if (now() - query.since < since) return r(); - - let done = { [sub]: [] }; - - let check = (s) => { - let elapsed = Date.now() - start; - if (rejected) return true; - if ( - done[s] && - (done[s].length === pool.relays.length || - (done[s].length && elapsed > eager)) - ) { - r(); - if (timeouts[s]) timeouts[s].clear(); - return true; - } - }; - - timeouts[sub] = { - async clear() { - clearTimeout(this.timer); - delete timeouts[sub]; - pool.unsubscribe(sub); - await s(`since:${sub}`, now()); - }, - extend(ev) { - if (ev) { - if (seen.includes(ev.id)) return; - seen.push(ev.id); - seen.length > 1000 && seen.shift(); - } - - clearTimeout(this.timer); - this.timer = setTimeout(() => { - j(new Error(`query timed out: ${sub} ${Date.now()}`)); - pool.unsubscribe(sub); - rejected = true; - }, timeout); - }, - timer: undefined, - }; - - timeouts[sub].extend(); - pool.subscribe(sub, query); - - pool.on("eose", (relay, s) => { - if (done[s]) done[s].push(relay.url); - check(s); - }); - - try { - await wait(() => check(sub), 100, 200); - } catch (e) { - console.log(e); - } - }); - -let seen = []; - -let getUser = async (pubkey) => { - let id = await g(`user:${pubkey}`); - let user = await g(`user:${id}`); - return ( - user || { - username: pubkey.substr(0, 6), - pubkey, - anon: true, - } - ); -}; - -function send(ev, url, opts) { - let timeout = (opts && opts.timeout != null && opts.timeout) || 1000; - - return new Promise((resolve, reject) => { - let relay = Relay(url); - - function timeout_reached() { - relay.close(); - reject(new Error("Request timeout")); - } - - let timer = setTimeout(timeout_reached, timeout); - - relay.on("open", () => { - clearTimeout(timer); - timer = setTimeout(timeout_reached, timeout); - relay.send(["EVENT", ev]); - }); - - relay.on("ok", (evid, ok, msg) => { - clearTimeout(timer); - relay.close(); - resolve({ evid, ok, msg }); - }); - }); -} - -export async function handleZap(invoice) { - let { data: privkey } = nip19.decode(config.nostrKey); - let pubkey = getPublicKey(privkey); - let keypair = { privkey, pubkey }; - let zapreq = JSON.parse(invoice.description); - - if (!zapreq.tags || zapreq.tags.length === 0) { - fail(`No tags found`); - } - - let ptags = zapreq.tags.filter( - (t) => t && t.length && t.length >= 2 && t[0] === "p", - ); - - if (ptags.length !== 1) { - fail(`None or multiple p tags found`); - } - - let etags = zapreq.tags.filter( - (t) => t && t.length && t.length >= 2 && t[0] === "e", - ); - - if (!(etags.length === 0 || etags.length === 1)) { - fail(`Expected none or 1 e tags`); - } - - let relays_tag = zapreq.tags.find( - (t) => t && t.length && t.length >= 2 && t[0] === "relays", - ); - - if (!relays_tag) { - fail(`No relays tag found`); - } - - let relays = relays_tag.slice(1).filter((r) => r && r.startsWith("ws")); - let etag = etags.length > 0 && etags[0]; - let ptag = ptags[0]; - - let kind = 9735; - let created_at = invoice.paid_at; - let content = zapreq.content; - - let tags = [ptag]; - if (etag) tags.push(etag); - - tags.push(["bolt11", invoice.bolt11]); - tags.push(["description", invoice.description]); - tags.push(["preimage", invoice.payment_preimage]); - - let ev = { pubkey, kind, created_at, content, tags }; - ev.id = await calculateId(ev); - ev.sig = await signId(privkey, ev.id); - - l("sending receipt"); - - await Promise.allSettled(relays.map((r) => send(ev, r))); -} - -// let damus = new Relay("ws://strfry:7777"); -// damus.on("open", (relay) => { -// damus.subscribe("nwc", { limit: 1, kinds: [23194] }); -// }); -// -// damus.on("event", async (sub, ev) => { -// console.log("EV", ev); -// }); diff --git a/lib/nostr.js.map b/lib/nostr.js.map new file mode 100644 index 00000000..46c97623 --- /dev/null +++ b/lib/nostr.js.map @@ -0,0 +1 @@ +{"version":3,"file":"nostr.js","sourceRoot":"","sources":["nostr.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/nostr.ts b/lib/nostr.ts new file mode 100644 index 00000000..ccfb4e54 --- /dev/null +++ b/lib/nostr.ts @@ -0,0 +1,280 @@ +import config from "$config"; +import { l } from "$lib/logging"; +import { Relay, RelayPool, calculateId, signId, getPublicKey } from "nostr"; +import { broadcast, emit } from "$lib/sockets"; +import store from "$lib/store"; +import { g, s, db } from "$lib/db"; +import { fail, nada, wait } from "$lib/utils"; +import { nip19 } from "nostr-tools"; + +export const COINOS_PUBKEY = getPublicKey(nip19.decode(config.nostrKey).data); +export let coinos; +export let pool; + +export let fillPool = () => { + try { + pool = RelayPool(config.relays); + pool.on("open", (relay) => { + if (relay.url.includes(config.nostr)) coinos = relay; + relay.subscribe("live", { limit: 1 }); + }); + + pool.on("event", async (relay, sub, ev) => { + if (!coinos) return; + if (timeouts[sub]) timeouts[sub].extend(ev); + try { + let content; + try { + content = JSON.parse(ev.content); + } catch (e) { + content = ev.content; + } + + if (sub === "live") { + let { pubkey } = ev; + + if (Math.abs(Math.floor(Date.now() / 1000) - ev.created_at) > 7200) + return; + + if (seen.includes(ev.id)) return; + seen.push(ev.id); + seen.length > 1000 && seen.shift(); + + if (coinos && ev.kind < 5) { + // coinos.send(["EVENT", ev]); + ev.user = await getUser(pubkey); + // broadcast("event", ev); + + if (ev.kind === 4) { + let uid = await g(`user:${ev.tags[0][1]}`); + if (uid) ev.recipient = await g(`user:${uid}`); + ev.author = ev.user; + emit(uid, "event", ev); + } + } + } else if (sub.includes("profile")) { + let { pubkey } = ev; + let user = await getUser(pubkey); + + if (user.updated > ev.created_at) return; + + if (content.name && user.username === pubkey.substr(0, 6)) + user.username = content.name; + + delete content.name; + + user = { + ...user, + ...content, + updated: ev.created_at, + }; + + await s(`user:${pubkey}`, JSON.stringify(user)); + } else if (sub.includes("messages")) { + let pubkey = sub.split(":")[0]; + await db.sAdd(`${pubkey}:messages`, ev.id); + await s(`ev:${ev.id}`, ev); + } else if (sub.includes("follows")) { + let pubkey = sub.split(":")[0]; + await s(`${pubkey}:follows`, ev.tags); + for (let f of ev.tags) { + let [_, followPubkey] = f; + await db.sAdd(`${followPubkey}:followers`, pubkey); + } + coinos.send(ev); + } else if (sub.includes("followers")) { + let followed = sub.split(":")[0]; + let { pubkey } = ev; + await db.sAdd(`${followed}:followers`, pubkey); + coinos.send(ev); + } else if (sub.includes("notes")) { + let pubkey = sub.split(":")[0]; + await db.sAdd(pubkey, ev.id); + await s(`ev:${ev.id}`, ev); + } + } catch (e) { + console.log(e); + } + }); + } catch(e) { + console.log("POOL", e) + } +}; + +let now = () => Math.round(Date.now() / 1000); + +let timeouts = {}; +export let q = async ( + sub, + query, + { timeout = 20000, since = 3600, eager = 200 }, +) => + new Promise(async (r, j) => { + let start = Date.now(); + let seen = []; + let rejected; + // query.since = await g(`since:${sub}`); + // if (now() - query.since < since) return r(); + + let done = { [sub]: [] }; + + let check = (s) => { + let elapsed = Date.now() - start; + if (rejected) return true; + if ( + done[s] && + (done[s].length === pool.relays.length || + (done[s].length && elapsed > eager)) + ) { + r(); + if (timeouts[s]) timeouts[s].clear(); + return true; + } + }; + + timeouts[sub] = { + async clear() { + clearTimeout(this.timer); + delete timeouts[sub]; + pool.unsubscribe(sub); + await s(`since:${sub}`, now()); + }, + extend(ev) { + if (ev) { + if (seen.includes(ev.id)) return; + seen.push(ev.id); + seen.length > 1000 && seen.shift(); + } + + clearTimeout(this.timer); + this.timer = setTimeout(() => { + j(new Error(`query timed out: ${sub} ${Date.now()}`)); + pool.unsubscribe(sub); + rejected = true; + }, timeout); + }, + timer: undefined, + }; + + timeouts[sub].extend(); + pool.subscribe(sub, query); + + pool.on("eose", (relay, s) => { + if (done[s]) done[s].push(relay.url); + check(s); + }); + + try { + await wait(() => check(sub), 100, 200); + } catch (e) { + console.log(e); + } + }); + +let seen = []; + +let getUser = async (pubkey) => { + let id = await g(`user:${pubkey}`); + let user = await g(`user:${id}`); + return ( + user || { + username: pubkey.substr(0, 6), + pubkey, + anon: true, + } + ); +}; + +function send(ev, url, opts) { + let timeout = (opts && opts.timeout != null && opts.timeout) || 1000; + + return new Promise((resolve, reject) => { + let relay = Relay(url); + + function timeout_reached() { + relay.close(); + reject(new Error("Request timeout")); + } + + let timer = setTimeout(timeout_reached, timeout); + + relay.on("open", () => { + clearTimeout(timer); + timer = setTimeout(timeout_reached, timeout); + relay.send(["EVENT", ev]); + }); + + relay.on("ok", (evid, ok, msg) => { + clearTimeout(timer); + relay.close(); + resolve({ evid, ok, msg }); + }); + }); +} + +export async function handleZap(invoice) { + let { data: privkey } = nip19.decode(config.nostrKey); + let pubkey = getPublicKey(privkey); + let keypair = { privkey, pubkey }; + let zapreq = JSON.parse(invoice.description); + + if (!zapreq.tags || zapreq.tags.length === 0) { + fail(`No tags found`); + } + + let ptags = zapreq.tags.filter( + (t) => t && t.length && t.length >= 2 && t[0] === "p", + ); + + if (ptags.length !== 1) { + fail(`None or multiple p tags found`); + } + + let etags = zapreq.tags.filter( + (t) => t && t.length && t.length >= 2 && t[0] === "e", + ); + + if (!(etags.length === 0 || etags.length === 1)) { + fail(`Expected none or 1 e tags`); + } + + let relays_tag = zapreq.tags.find( + (t) => t && t.length && t.length >= 2 && t[0] === "relays", + ); + + if (!relays_tag) { + fail(`No relays tag found`); + } + + let relays = relays_tag.slice(1).filter((r) => r && r.startsWith("ws")); + let etag = etags.length > 0 && etags[0]; + let ptag = ptags[0]; + + let kind = 9735; + let created_at = invoice.paid_at; + let content = zapreq.content; + + let tags = [ptag]; + if (etag) tags.push(etag); + + tags.push(["bolt11", invoice.bolt11]); + tags.push(["description", invoice.description]); + tags.push(["preimage", invoice.payment_preimage]); + + let ev = { pubkey, kind, created_at, content, tags }; + ev.id = await calculateId(ev); + ev.sig = await signId(privkey, ev.id); + + l("sending receipt"); + + await Promise.allSettled(relays.map((r) => send(ev, r))); +} + +// let damus = new Relay("ws://strfry:7777"); +// damus.on("open", (relay) => { +// damus.subscribe("nwc", { limit: 1, kinds: [23194] }); +// }); +// +// damus.on("event", async (sub, ev) => { +// console.log("EV", ev); +// }); diff --git a/lib/notifications.js b/lib/notifications.js deleted file mode 100644 index af47a6d7..00000000 --- a/lib/notifications.js +++ /dev/null @@ -1,38 +0,0 @@ -import config from "$config"; -import webpush from "web-push"; - -if (config.vapid) { - webpush.setVapidDetails( - config.vapid.url, - config.vapid.publicKey, - config.vapid.privateKey, - ); -} - -export const notify = async (user, payload) => { - try { - let { subscriptions } = user; - - if (subscriptions) { - let i = 0; - for (let subscription of subscriptions) { - i++; - try { - await webpush.sendNotification(subscription, payload); - } catch (e) { - if ( - e.body && - (e.body.includes("expired") || e.body.includes("No such")) - ) { - user.subscriptions.splice(i, 1); - await user.save(); - } else { - err("problem sending notification", e); - } - } - } - } - } catch (e) { - console.log("problem notifying", e); - } -}; diff --git a/lib/notifications.js.map b/lib/notifications.js.map new file mode 100644 index 00000000..0052e1f8 --- /dev/null +++ b/lib/notifications.js.map @@ -0,0 +1 @@ +{"version":3,"file":"notifications.js","sourceRoot":"","sources":["notifications.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAA6B;AAC7B,sDAA+B;AAE/B,IAAI,iBAAM,CAAC,KAAK,EAAE,CAAC;IACjB,kBAAO,CAAC,eAAe,CACrB,iBAAM,CAAC,KAAK,CAAC,GAAG,EAChB,iBAAM,CAAC,KAAK,CAAC,SAAS,EACtB,iBAAM,CAAC,KAAK,CAAC,UAAU,CACxB,CAAC;AACJ,CAAC;AAEM,IAAM,MAAM,GAAG,UAAO,IAAI,EAAE,OAAO;;;;;;gBAEhC,aAAa,GAAK,IAAI,cAAT,CAAU;qBAEzB,aAAa,EAAb,wBAAa;gBACX,CAAC,GAAG,CAAC,CAAC;sBAC4B,EAAb,+BAAa;;;qBAAb,CAAA,2BAAa,CAAA;gBAA7B,YAAY;gBACnB,CAAC,EAAE,CAAC;;;;gBAEF,qBAAM,kBAAO,CAAC,gBAAgB,CAAC,YAAY,EAAE,OAAO,CAAC,EAAA;;gBAArD,SAAqD,CAAC;;;;qBAGpD,CAAA,GAAC,CAAC,IAAI;oBACN,CAAC,GAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,GAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA,EAD1D,wBAC0D;gBAE1D,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAChC,qBAAM,IAAI,CAAC,IAAI,EAAE,EAAA;;gBAAjB,SAAiB,CAAC;;;gBAElB,GAAG,CAAC,8BAA8B,EAAE,GAAC,CAAC,CAAC;;;;gBAZpB,IAAa,CAAA;;;;;gBAkBxC,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,GAAC,CAAC,CAAC;;;;;KAEvC,CAAC;AA1BW,QAAA,MAAM,UA0BjB"} \ No newline at end of file diff --git a/lib/notifications.ts b/lib/notifications.ts new file mode 100644 index 00000000..af47a6d7 --- /dev/null +++ b/lib/notifications.ts @@ -0,0 +1,38 @@ +import config from "$config"; +import webpush from "web-push"; + +if (config.vapid) { + webpush.setVapidDetails( + config.vapid.url, + config.vapid.publicKey, + config.vapid.privateKey, + ); +} + +export const notify = async (user, payload) => { + try { + let { subscriptions } = user; + + if (subscriptions) { + let i = 0; + for (let subscription of subscriptions) { + i++; + try { + await webpush.sendNotification(subscription, payload); + } catch (e) { + if ( + e.body && + (e.body.includes("expired") || e.body.includes("No such")) + ) { + user.subscriptions.splice(i, 1); + await user.save(); + } else { + err("problem sending notification", e); + } + } + } + } + } catch (e) { + console.log("problem notifying", e); + } +}; diff --git a/lib/payments.js b/lib/payments.js deleted file mode 100644 index 92e17839..00000000 --- a/lib/payments.js +++ /dev/null @@ -1,682 +0,0 @@ -import locales from "$lib/locales/index"; -import config from "$config"; -import { emit } from "$lib/sockets"; -import { v4 } from "uuid"; -import { notify } from "$lib/notifications"; -import { db, g, s, t } from "$lib/db"; -import { l, err, warn } from "$lib/logging"; -import { - f, - btc, - fail, - fiat, - getInvoice, - getUser, - sleep, - wait, - SATS, - sats, - formatReceipt, -} from "$lib/utils"; -import { callWebhook } from "$lib/webhooks"; -import got from "got"; -import { mqtt1, mqtt2 } from "$lib/mqtt"; -import { mail, templates } from "$lib/mail"; -import api from "$lib/api"; - -import lq from "$lib/liquid"; -import bc from "$lib/bitcoin"; -import ln from "$lib/ln"; - -let { URL } = process.env; - -export let types = { - internal: "internal", - bitcoin: "bitcoin", - lightning: "lightning", - fund: "fund", - liquid: "liquid", - ecash: "ecash", -}; - -export let debit = async ({ - hash, - amount, - fee = 0, - memo, - user, - type = types.internal, - rate, -}) => { - amount = parseInt(amount); - if (type !== types.internal && (await g("freeze"))) - fail("Problem sending payment"); - // if (!user.unlimited && amount > 1000000) - // fail(`⚡️${amount} exceeds max withdrawal of ⚡️1,000,000`); - let ref; - let { id: uid, currency, username } = user; - - let rates = await g('rates'); - if (!rate) rate = rates[currency]; - - let invoice = await getInvoice(hash); - let iid; - - if (invoice) { - if (invoice.received >= amount) fail("Invoice already paid"); - ({ id: iid } = invoice); - - ref = invoice.uid; - - let equivalentRate = - invoice.rate * (rates[currency] / rates[invoice.currency]); - - if (Math.abs(invoice.rate / rates[invoice.currency] - 1) < 0.01) { - rate = equivalentRate; - } else { - warn("rate slipped", hash, invoice.rate, equivalentRate); - } - } - - let tip = parseInt(invoice?.tip) || null; - if (tip < 0) fail("Invalid tip"); - - if (!amount || amount < 0) fail("Amount must be greater than zero"); - - let ourfee = [types.bitcoin, types.liquid, types.lightning].includes(type) - ? Math.round((amount + fee + tip) * config.fee) - : 0; - - ourfee = await db.debit( - `balance:${uid}`, - `credit:${type}:${uid}`, - amount || 0, - tip || 0, - fee || 0, - ourfee || 0, - ); - - if (ourfee.err) fail(ourfee.err); - - let id = v4(); - let p = { - id, - amount: -amount, - fee, - hash, - ourfee, - memo, - iid, - uid, - confirmed: ![types.bitcoin, types.liquid].includes(type), - rate, - currency, - type, - ref, - tip, - created: Date.now(), - }; - - await s(`payment:${hash}`, id); - await s(`payment:${id}`, p); - await db.lPush(`${uid}:payments`, id); - - l(user.username, "sent", type, amount); - //emit(user.id, "payment", p); - - return p; -}; - -export let credit = async (hash, amount, memo, ref, type = types.internal) => { - amount = parseInt(amount) || 0; - - let inv = await getInvoice(hash); - if (!inv) { - await db.sAdd("missing", ref.split(":")[0]); - return; - } - - let { tip } = inv; - tip = parseInt(tip) || 0; - - if (!memo) ({ memo } = inv); - if (memo && memo.length > 5000) fail("memo too long"); - if (amount < 0 || tip < 0) fail("Invalid amount"); - if (type === types.internal) amount += tip; - - let user = await g(`user:${inv.uid}`); - let { id: uid, currency, username } = user; - - let rates = await g('rates'); - let rate = rates[currency]; - - if (!rate) await sleep(1000); - rate = rates[currency]; - - let equivalentRate = - inv.rate * (rates[currency] / rates[inv.currency]); - - if (Math.abs(inv.rate / rates[inv.currency] - 1) < 0.01) { - rate = equivalentRate; - } else { - // warn("rate slipped", hash, invoice.rate, equivalentRate); - } - - let id = v4(); - let p = { - id, - iid: inv.id, - hash, - amount: parseInt(amount - tip), - uid, - rate, - currency, - memo, - ref, - tip, - type, - confirmed: true, - created: Date.now(), - }; - - if ([types.bitcoin, types.liquid].includes(type)) inv.pending += amount; - else { - inv.received += amount; - inv.preimage = ref; - } - - let balanceKey = "balance"; - if ([types.bitcoin, types.liquid].includes(type)) { - let [txid, vout] = ref.split(":").slice(-2); - p.confirmed = false; - balanceKey = "pending"; - await s(`payment:${txid}:${vout}`, id); - } else { - await s(`payment:${hash}`, id); - } - - let m = await db.multi(); - - if ([types.bitcoin, types.liquid, types.lightning].includes(type)) - m.incrBy(`credit:${type}:${uid}`, Math.round(amount * config.fee)); - - m.set(`invoice:${inv.id}`, JSON.stringify(inv)) - .set(`payment:${p.id}`, JSON.stringify(p)) - .lPush(`${uid}:payments`, p.id) - .incrBy(`${balanceKey}:${uid}`, amount) - .exec(); - - emit(uid, "payment", p); - notify(user, `Received ${amount} sats`); - callWebhook(inv, p); - l(username, "received", type, amount); - - let items; - if (inv.items && inv.items.length) { - formatReceipt(inv.items, inv.currency); - p.items = inv.items; - } - - if (config.mqtt1) { - mqtt1.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, - ); - } - - if (config.mqtt2) { - mqtt2.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, - ); - } - - try { - await completePayment(p, user); - } catch (e) { - console.log(e); - } - - return p; -}; - -let fmt = (sats) => - "⚡️" + - new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(sats); - -export let completePayment = async (p, user) => { - let { id, autowithdraw, threshold, reserve, destination, username } = user; - let link = (id) => `${URL}/payment/${id}`; - if (p.confirmed) { - let withdrawal; - if (autowithdraw) { - try { - let to = destination.trim(); - let balance = await g(`balance:${id}`); - let amount = balance - reserve; - if (balance > threshold) { - l("initiating autowithdrawal", amount, to, balance, threshold); - let w = await pay({ amount, to, user }); - withdrawal = { - amount: fmt(-w.amount), - link: link(w.id), - }; - } - } catch (e) { - withdrawal = { failed: true }; - warn(username, "autowithdraw failed", e.message); - } - } - - let t = locales[user.language || "en"]; - if (user.verified && user.notify) { - mail(user, t.paymentReceived, templates.paymentReceived, { - ...t, - username, - payment: { - amount: fmt(p.amount), - link: link(p.id), - tip: p.tip ? fmt(p.tip) : undefined, - fiat: f(fiat(p.amount, p.rate), p.currency), - fiatTip: p.tip ? f(fiat(p.tip, p.rate), p.currency) : undefined, - memo: p.memo, - items: - p.items && - p.items.map((i) => { - return { - quantity: i.quantity, - name: i.name, - total: i.quantity * i.price, - totalFiat: f(i.quantity * i.price, p.currency), - }; - }), - }, - withdrawal, - }); - } - } -}; - -let pay = async ({ amount, to, user }) => { - amount = parseInt(amount) || 0; - let lnurl, pr; - if (to.includes("@") && to.includes(".")) { - let [name, domain] = to.split("@"); - lnurl = `https://${domain}/.well-known/lnurlp/${name}`; - } else if (to.startsWith("lnurl")) { - lnurl = Buffer.from( - bech32.fromWords(bech32.decode(text, 20000).words), - ).toString(); - } - - let maxfee = getMaxFee(amount); - if (lnurl) { - amount -= maxfee; - let { callback } = await got(lnurl).json(); - ({ pr } = await got(`${callback}?amount=${amount * 1000}`).json()); - } else if (to.startsWith("ln")) { - amount -= maxfee; - pr = to; - } - - return pr - ? await sendLightning({ user, pr, amount, maxfee }) - : await sendOnchain({ amount, address: to, user, subtract: true }); -}; - -export let decode = async (hex) => { - let type, tx; - try { - tx = await bc.decodeRawTransaction(hex); - type = types.bitcoin; - } catch (e) { - try { - tx = await lq.decodeRawTransaction(hex); - type = types.liquid; - } catch (e) { - err("invalid hex", hex); - fail("unrecognized tx"); - } - } - - return { tx, type }; -}; - -export let sendOnchain = async (params) => { - let { hex, rate, user } = params; - if (!hex) ({ hex } = await build(params)); - - let { tx, type } = await decode(hex); - let node = getNode(type); - let { txid } = tx; - - try { - if (inflight[txid]) fail("payment in flight"); - inflight[txid] = true; - - if (config[type].walletpass) - await node.walletPassphrase(config[type].walletpass, 300); - - let { hex: signed } = await node.signRawTransactionWithWallet( - type === types.liquid ? await node.blindRawTransaction(hex) : hex, - ); - - ({ txid } = await node.decodeRawTransaction(signed)); - - let r = await node.testMempoolAccept([signed]); - if (!r[0].allowed) fail("transaction rejected"); - - let total = 0; - let fee = 0; - let change = 0; - - if (type === types.liquid) { - for (let { - asset, - scriptPubKey: { address, type }, - value, - } of tx.vout) { - if (asset !== config.liquid.btc) fail("only L-BTC supported"); - if (type === "fee") fee = sats(value); - else { - total += sats(value); - - if (address) { - if ((await node.getAddressInfo(address)).ismine) { - change += sats(value); - } - } - } - } - } else { - let totalIn = 0; - for await (let { txid, vout } of tx.vin) { - let hex = await node.getRawTransaction(txid); - let tx = await node.decodeRawTransaction(hex); - totalIn += sats(tx.vout[vout].value); - } - - for (let { - scriptPubKey: { address }, - value, - } of tx.vout) { - total += sats(value); - if (await g(`invoice:${address}`)) - fail("Cannot send to internal address"); - - if ((await node.getAddressInfo(address)).ismine) change += sats(value); - } - - fee = totalIn - total; - } - - let amount = total - change; - - let p = await debit({ - hash: txid, - amount, - fee, - rate, - user, - type, - }); - - p.hex = signed; - await s(`payment:${p.id}`, p); - - await node.sendRawTransaction(signed); - - delete inflight[txid]; - return p; - } catch (e) { - delete inflight[txid]; - throw e; - } -}; - -let getMaxFee = (n) => - Math.round( - n < 100 - ? n * 5 - : n < 1000 - ? n - : n < 10000 - ? n * 0.5 - : n < 100000 - ? n * 0.1 - : n < 1000000 - ? n * 0.05 - : n * 0.01, - ); - -let checking = {}; -let inflight = {}; -export let sendLightning = async ({ user, pr, amount, maxfee, memo }) => { - try { - if (inflight[user.id]) fail("payment in flight"); - inflight[user.id] = true; - - let p; - let hash = pr; - - if (typeof amount !== "undefined") { - amount = parseInt(amount); - if (amount < 0 || amount > SATS || isNaN(amount)) fail("Invalid amount"); - } - - let total = amount; - let { amount_msat, payment_hash } = await ln.decode(pr); - if (amount_msat) total = Math.round(amount_msat / 1000); - - maxfee = parseInt(maxfee) || getMaxFee(total); - - if (maxfee < 0) fail("Max fee cannot be negative"); - - let invoice = await getInvoice(hash); - - if (invoice) { - if (invoice.uid === user.id) fail("Cannot send to self"); - hash = pr; - } else { - let r; - - let { pays } = await ln.listpays(pr); - if (pays.find((p) => p.status === "complete")) - fail("Invoice has already been paid"); - - if (pays.find((p) => p.status === "pending")) - fail("Payment is already underway"); - - p = await debit({ - hash: pr, - amount: total, - fee: maxfee, - memo, - user, - type: types.lightning, - }); - - let check = async () => { - if (checking[pr]) return; - checking[pr] = true; - - try { - let { pays } = await ln.listpays(pr); - - let recordExists = !!(await g(`payment:${pr}`)); - let paymentFailed = - !pays.length || pays.every((p) => p.status === "failed"); - - if (recordExists && paymentFailed) { - await db.del(`payment:${pr}`); - await db.del(`payment:${p.id}`); - - let ourfee = p.ourfee || 0; - let credit = Math.round(total * config.fee) - ourfee; - await db.incrBy(`balance:${p.uid}`, total + maxfee + ourfee); - - await db.incrBy(`credit:${types.lightning}:${p.uid}`, credit); - await db.lRem(`${p.uid}:payments`, 1, p.id); - - clearInterval(interval); - } - } catch (e) { - err("Failed to reverse payment", r); - } - - checking[pr] = false; - }; - - let interval = setInterval(check, 5000); - - try { - l("paying lightning invoice", pr.substr(-8), total, amount, maxfee); - - r = await ln.pay({ - bolt11: pr.replace(/\s/g, "").toLowerCase(), - amount_msat: amount_msat ? undefined : amount * 1000, - maxfee: maxfee * 1000, - retry_for: 5, - }); - - if (!(r.status === "complete" && r.payment_preimage)) - fail("Payment did not complete"); - - l("payment completed", p.id, r.payment_preimage); - - p.amount = -total; - p.fee = Math.round((r.amount_sent_msat - r.amount_msat) / 1000); - p.ref = r.payment_preimage; - - await s(`payment:${p.id}`, p); - - l("refunding fee", maxfee, p.fee, maxfee - p.fee, p.ref); - await db.incrBy(`balance:${p.uid}`, maxfee - p.fee); - } catch (e) { - await check(); - throw e; - } - - clearInterval(interval); - } - - delete inflight[user.id]; - return p; - } catch (e) { - delete inflight[user.id]; - throw e; - } -}; - -export let getNode = (type) => { - if (type === types.bitcoin) return bc; - else if (type === types.liquid) return lq; - else fail("unrecognized transaction type"); -}; - -let getAddressType = async (a) => { - try { - await bc.getAddressInfo(a); - return types.bitcoin; - } catch (e) { - try { - await lq.getAddressInfo(a); - return types.liquid; - } catch (e) { - fail("unrecognized address"); - } - } -}; - -export let build = async ({ amount, address, feeRate, user, subtract }) => { - let type = await getAddressType(address); - - let node = getNode(type); - amount = parseInt(amount); - if (amount < 0) fail("invalid amount"); - - let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => r.json()); - - if (!feeRate) { - feeRate = fees.halfHourFee; - } - - if (feeRate < fees.economyFee) fail("fee rate too low"); - - let replaceable = false; - - let outs = [{ [address]: btc(amount) }]; - - if (type === types.liquid) - outs = outs.map((o) => ({ ...o, asset: config.liquid.btc })); - - let raw = await node.createRawTransaction([], outs, 0, replaceable); - - let tx = await node.fundRawTransaction(raw, { - fee_rate: feeRate, - replaceable, - subtractFeeFromOutputs: [], - }); - - let fee = sats(tx.fee); - let dust = 547; - - let balance = await g(`balance:${user.id}`); - let ourfee = Math.round(amount * config.fee); - let credit = await g(`credit:${type}:${user.id}`); - let covered = Math.min(credit, ourfee); - ourfee -= covered; - - if (subtract || amount + fee + ourfee > balance) { - if (amount <= fee + ourfee + dust) - fail( - `insufficient funds ⚡️${balance} of ⚡️${amount + fee + ourfee + dust}`, - ); - - outs = [{ [address]: btc(amount - ourfee) }]; - raw = await node.createRawTransaction([], outs, 0, replaceable); - - tx = await node.fundRawTransaction(raw, { - fee_rate: feeRate, - replaceable, - subtractFeeFromOutputs: [0], - }); - - fee = sats(tx.fee); - } - - return { feeRate, ourfee, fee, fees, hex: tx.hex }; -}; - -export let catchUp = async () => { - try { - let txns = []; - for (let [type, n] of Object.entries({ bitcoin: bc, liquid: lq })) { - txns.push( - ...(await n.listTransactions("*", 200)).filter((tx) => { - tx.type = type; - return tx.category === "receive" && tx.confirmations > 0; - }), - ); - } - - for (let { txid, type } of txns) { - try { - if (await db.zScore("seen", txid)) continue; - await got.post(`http://localhost:${process.env.PORT || 3119}/confirm`, { - json: { txid, wallet: config.bitcoin.wallet, type }, - }); - - await db.zAdd("seen", { score: Date.now(), value: txid }); - if ((await db.zCard("seen")) > 10000) - await db.zRemRangeByRank("seen", 0, 0); - } catch (e) { - err("problem confirming", e.message); - } - } - } catch (e) { - err("problem syncing", e.message); - } - - setTimeout(catchUp, 2000); -}; diff --git a/lib/payments.js.map b/lib/payments.js.map new file mode 100644 index 00000000..c4cf897b --- /dev/null +++ b/lib/payments.js.map @@ -0,0 +1 @@ +{"version":3,"file":"payments.js","sourceRoot":"","sources":["payments.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/payments.ts b/lib/payments.ts new file mode 100644 index 00000000..92e17839 --- /dev/null +++ b/lib/payments.ts @@ -0,0 +1,682 @@ +import locales from "$lib/locales/index"; +import config from "$config"; +import { emit } from "$lib/sockets"; +import { v4 } from "uuid"; +import { notify } from "$lib/notifications"; +import { db, g, s, t } from "$lib/db"; +import { l, err, warn } from "$lib/logging"; +import { + f, + btc, + fail, + fiat, + getInvoice, + getUser, + sleep, + wait, + SATS, + sats, + formatReceipt, +} from "$lib/utils"; +import { callWebhook } from "$lib/webhooks"; +import got from "got"; +import { mqtt1, mqtt2 } from "$lib/mqtt"; +import { mail, templates } from "$lib/mail"; +import api from "$lib/api"; + +import lq from "$lib/liquid"; +import bc from "$lib/bitcoin"; +import ln from "$lib/ln"; + +let { URL } = process.env; + +export let types = { + internal: "internal", + bitcoin: "bitcoin", + lightning: "lightning", + fund: "fund", + liquid: "liquid", + ecash: "ecash", +}; + +export let debit = async ({ + hash, + amount, + fee = 0, + memo, + user, + type = types.internal, + rate, +}) => { + amount = parseInt(amount); + if (type !== types.internal && (await g("freeze"))) + fail("Problem sending payment"); + // if (!user.unlimited && amount > 1000000) + // fail(`⚡️${amount} exceeds max withdrawal of ⚡️1,000,000`); + let ref; + let { id: uid, currency, username } = user; + + let rates = await g('rates'); + if (!rate) rate = rates[currency]; + + let invoice = await getInvoice(hash); + let iid; + + if (invoice) { + if (invoice.received >= amount) fail("Invoice already paid"); + ({ id: iid } = invoice); + + ref = invoice.uid; + + let equivalentRate = + invoice.rate * (rates[currency] / rates[invoice.currency]); + + if (Math.abs(invoice.rate / rates[invoice.currency] - 1) < 0.01) { + rate = equivalentRate; + } else { + warn("rate slipped", hash, invoice.rate, equivalentRate); + } + } + + let tip = parseInt(invoice?.tip) || null; + if (tip < 0) fail("Invalid tip"); + + if (!amount || amount < 0) fail("Amount must be greater than zero"); + + let ourfee = [types.bitcoin, types.liquid, types.lightning].includes(type) + ? Math.round((amount + fee + tip) * config.fee) + : 0; + + ourfee = await db.debit( + `balance:${uid}`, + `credit:${type}:${uid}`, + amount || 0, + tip || 0, + fee || 0, + ourfee || 0, + ); + + if (ourfee.err) fail(ourfee.err); + + let id = v4(); + let p = { + id, + amount: -amount, + fee, + hash, + ourfee, + memo, + iid, + uid, + confirmed: ![types.bitcoin, types.liquid].includes(type), + rate, + currency, + type, + ref, + tip, + created: Date.now(), + }; + + await s(`payment:${hash}`, id); + await s(`payment:${id}`, p); + await db.lPush(`${uid}:payments`, id); + + l(user.username, "sent", type, amount); + //emit(user.id, "payment", p); + + return p; +}; + +export let credit = async (hash, amount, memo, ref, type = types.internal) => { + amount = parseInt(amount) || 0; + + let inv = await getInvoice(hash); + if (!inv) { + await db.sAdd("missing", ref.split(":")[0]); + return; + } + + let { tip } = inv; + tip = parseInt(tip) || 0; + + if (!memo) ({ memo } = inv); + if (memo && memo.length > 5000) fail("memo too long"); + if (amount < 0 || tip < 0) fail("Invalid amount"); + if (type === types.internal) amount += tip; + + let user = await g(`user:${inv.uid}`); + let { id: uid, currency, username } = user; + + let rates = await g('rates'); + let rate = rates[currency]; + + if (!rate) await sleep(1000); + rate = rates[currency]; + + let equivalentRate = + inv.rate * (rates[currency] / rates[inv.currency]); + + if (Math.abs(inv.rate / rates[inv.currency] - 1) < 0.01) { + rate = equivalentRate; + } else { + // warn("rate slipped", hash, invoice.rate, equivalentRate); + } + + let id = v4(); + let p = { + id, + iid: inv.id, + hash, + amount: parseInt(amount - tip), + uid, + rate, + currency, + memo, + ref, + tip, + type, + confirmed: true, + created: Date.now(), + }; + + if ([types.bitcoin, types.liquid].includes(type)) inv.pending += amount; + else { + inv.received += amount; + inv.preimage = ref; + } + + let balanceKey = "balance"; + if ([types.bitcoin, types.liquid].includes(type)) { + let [txid, vout] = ref.split(":").slice(-2); + p.confirmed = false; + balanceKey = "pending"; + await s(`payment:${txid}:${vout}`, id); + } else { + await s(`payment:${hash}`, id); + } + + let m = await db.multi(); + + if ([types.bitcoin, types.liquid, types.lightning].includes(type)) + m.incrBy(`credit:${type}:${uid}`, Math.round(amount * config.fee)); + + m.set(`invoice:${inv.id}`, JSON.stringify(inv)) + .set(`payment:${p.id}`, JSON.stringify(p)) + .lPush(`${uid}:payments`, p.id) + .incrBy(`${balanceKey}:${uid}`, amount) + .exec(); + + emit(uid, "payment", p); + notify(user, `Received ${amount} sats`); + callWebhook(inv, p); + l(username, "received", type, amount); + + let items; + if (inv.items && inv.items.length) { + formatReceipt(inv.items, inv.currency); + p.items = inv.items; + } + + if (config.mqtt1) { + mqtt1.publish( + username, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, + ); + } + + if (config.mqtt2) { + mqtt2.publish( + username, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}:${p.memo}:${items}`, + ); + } + + try { + await completePayment(p, user); + } catch (e) { + console.log(e); + } + + return p; +}; + +let fmt = (sats) => + "⚡️" + + new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(sats); + +export let completePayment = async (p, user) => { + let { id, autowithdraw, threshold, reserve, destination, username } = user; + let link = (id) => `${URL}/payment/${id}`; + if (p.confirmed) { + let withdrawal; + if (autowithdraw) { + try { + let to = destination.trim(); + let balance = await g(`balance:${id}`); + let amount = balance - reserve; + if (balance > threshold) { + l("initiating autowithdrawal", amount, to, balance, threshold); + let w = await pay({ amount, to, user }); + withdrawal = { + amount: fmt(-w.amount), + link: link(w.id), + }; + } + } catch (e) { + withdrawal = { failed: true }; + warn(username, "autowithdraw failed", e.message); + } + } + + let t = locales[user.language || "en"]; + if (user.verified && user.notify) { + mail(user, t.paymentReceived, templates.paymentReceived, { + ...t, + username, + payment: { + amount: fmt(p.amount), + link: link(p.id), + tip: p.tip ? fmt(p.tip) : undefined, + fiat: f(fiat(p.amount, p.rate), p.currency), + fiatTip: p.tip ? f(fiat(p.tip, p.rate), p.currency) : undefined, + memo: p.memo, + items: + p.items && + p.items.map((i) => { + return { + quantity: i.quantity, + name: i.name, + total: i.quantity * i.price, + totalFiat: f(i.quantity * i.price, p.currency), + }; + }), + }, + withdrawal, + }); + } + } +}; + +let pay = async ({ amount, to, user }) => { + amount = parseInt(amount) || 0; + let lnurl, pr; + if (to.includes("@") && to.includes(".")) { + let [name, domain] = to.split("@"); + lnurl = `https://${domain}/.well-known/lnurlp/${name}`; + } else if (to.startsWith("lnurl")) { + lnurl = Buffer.from( + bech32.fromWords(bech32.decode(text, 20000).words), + ).toString(); + } + + let maxfee = getMaxFee(amount); + if (lnurl) { + amount -= maxfee; + let { callback } = await got(lnurl).json(); + ({ pr } = await got(`${callback}?amount=${amount * 1000}`).json()); + } else if (to.startsWith("ln")) { + amount -= maxfee; + pr = to; + } + + return pr + ? await sendLightning({ user, pr, amount, maxfee }) + : await sendOnchain({ amount, address: to, user, subtract: true }); +}; + +export let decode = async (hex) => { + let type, tx; + try { + tx = await bc.decodeRawTransaction(hex); + type = types.bitcoin; + } catch (e) { + try { + tx = await lq.decodeRawTransaction(hex); + type = types.liquid; + } catch (e) { + err("invalid hex", hex); + fail("unrecognized tx"); + } + } + + return { tx, type }; +}; + +export let sendOnchain = async (params) => { + let { hex, rate, user } = params; + if (!hex) ({ hex } = await build(params)); + + let { tx, type } = await decode(hex); + let node = getNode(type); + let { txid } = tx; + + try { + if (inflight[txid]) fail("payment in flight"); + inflight[txid] = true; + + if (config[type].walletpass) + await node.walletPassphrase(config[type].walletpass, 300); + + let { hex: signed } = await node.signRawTransactionWithWallet( + type === types.liquid ? await node.blindRawTransaction(hex) : hex, + ); + + ({ txid } = await node.decodeRawTransaction(signed)); + + let r = await node.testMempoolAccept([signed]); + if (!r[0].allowed) fail("transaction rejected"); + + let total = 0; + let fee = 0; + let change = 0; + + if (type === types.liquid) { + for (let { + asset, + scriptPubKey: { address, type }, + value, + } of tx.vout) { + if (asset !== config.liquid.btc) fail("only L-BTC supported"); + if (type === "fee") fee = sats(value); + else { + total += sats(value); + + if (address) { + if ((await node.getAddressInfo(address)).ismine) { + change += sats(value); + } + } + } + } + } else { + let totalIn = 0; + for await (let { txid, vout } of tx.vin) { + let hex = await node.getRawTransaction(txid); + let tx = await node.decodeRawTransaction(hex); + totalIn += sats(tx.vout[vout].value); + } + + for (let { + scriptPubKey: { address }, + value, + } of tx.vout) { + total += sats(value); + if (await g(`invoice:${address}`)) + fail("Cannot send to internal address"); + + if ((await node.getAddressInfo(address)).ismine) change += sats(value); + } + + fee = totalIn - total; + } + + let amount = total - change; + + let p = await debit({ + hash: txid, + amount, + fee, + rate, + user, + type, + }); + + p.hex = signed; + await s(`payment:${p.id}`, p); + + await node.sendRawTransaction(signed); + + delete inflight[txid]; + return p; + } catch (e) { + delete inflight[txid]; + throw e; + } +}; + +let getMaxFee = (n) => + Math.round( + n < 100 + ? n * 5 + : n < 1000 + ? n + : n < 10000 + ? n * 0.5 + : n < 100000 + ? n * 0.1 + : n < 1000000 + ? n * 0.05 + : n * 0.01, + ); + +let checking = {}; +let inflight = {}; +export let sendLightning = async ({ user, pr, amount, maxfee, memo }) => { + try { + if (inflight[user.id]) fail("payment in flight"); + inflight[user.id] = true; + + let p; + let hash = pr; + + if (typeof amount !== "undefined") { + amount = parseInt(amount); + if (amount < 0 || amount > SATS || isNaN(amount)) fail("Invalid amount"); + } + + let total = amount; + let { amount_msat, payment_hash } = await ln.decode(pr); + if (amount_msat) total = Math.round(amount_msat / 1000); + + maxfee = parseInt(maxfee) || getMaxFee(total); + + if (maxfee < 0) fail("Max fee cannot be negative"); + + let invoice = await getInvoice(hash); + + if (invoice) { + if (invoice.uid === user.id) fail("Cannot send to self"); + hash = pr; + } else { + let r; + + let { pays } = await ln.listpays(pr); + if (pays.find((p) => p.status === "complete")) + fail("Invoice has already been paid"); + + if (pays.find((p) => p.status === "pending")) + fail("Payment is already underway"); + + p = await debit({ + hash: pr, + amount: total, + fee: maxfee, + memo, + user, + type: types.lightning, + }); + + let check = async () => { + if (checking[pr]) return; + checking[pr] = true; + + try { + let { pays } = await ln.listpays(pr); + + let recordExists = !!(await g(`payment:${pr}`)); + let paymentFailed = + !pays.length || pays.every((p) => p.status === "failed"); + + if (recordExists && paymentFailed) { + await db.del(`payment:${pr}`); + await db.del(`payment:${p.id}`); + + let ourfee = p.ourfee || 0; + let credit = Math.round(total * config.fee) - ourfee; + await db.incrBy(`balance:${p.uid}`, total + maxfee + ourfee); + + await db.incrBy(`credit:${types.lightning}:${p.uid}`, credit); + await db.lRem(`${p.uid}:payments`, 1, p.id); + + clearInterval(interval); + } + } catch (e) { + err("Failed to reverse payment", r); + } + + checking[pr] = false; + }; + + let interval = setInterval(check, 5000); + + try { + l("paying lightning invoice", pr.substr(-8), total, amount, maxfee); + + r = await ln.pay({ + bolt11: pr.replace(/\s/g, "").toLowerCase(), + amount_msat: amount_msat ? undefined : amount * 1000, + maxfee: maxfee * 1000, + retry_for: 5, + }); + + if (!(r.status === "complete" && r.payment_preimage)) + fail("Payment did not complete"); + + l("payment completed", p.id, r.payment_preimage); + + p.amount = -total; + p.fee = Math.round((r.amount_sent_msat - r.amount_msat) / 1000); + p.ref = r.payment_preimage; + + await s(`payment:${p.id}`, p); + + l("refunding fee", maxfee, p.fee, maxfee - p.fee, p.ref); + await db.incrBy(`balance:${p.uid}`, maxfee - p.fee); + } catch (e) { + await check(); + throw e; + } + + clearInterval(interval); + } + + delete inflight[user.id]; + return p; + } catch (e) { + delete inflight[user.id]; + throw e; + } +}; + +export let getNode = (type) => { + if (type === types.bitcoin) return bc; + else if (type === types.liquid) return lq; + else fail("unrecognized transaction type"); +}; + +let getAddressType = async (a) => { + try { + await bc.getAddressInfo(a); + return types.bitcoin; + } catch (e) { + try { + await lq.getAddressInfo(a); + return types.liquid; + } catch (e) { + fail("unrecognized address"); + } + } +}; + +export let build = async ({ amount, address, feeRate, user, subtract }) => { + let type = await getAddressType(address); + + let node = getNode(type); + amount = parseInt(amount); + if (amount < 0) fail("invalid amount"); + + let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => r.json()); + + if (!feeRate) { + feeRate = fees.halfHourFee; + } + + if (feeRate < fees.economyFee) fail("fee rate too low"); + + let replaceable = false; + + let outs = [{ [address]: btc(amount) }]; + + if (type === types.liquid) + outs = outs.map((o) => ({ ...o, asset: config.liquid.btc })); + + let raw = await node.createRawTransaction([], outs, 0, replaceable); + + let tx = await node.fundRawTransaction(raw, { + fee_rate: feeRate, + replaceable, + subtractFeeFromOutputs: [], + }); + + let fee = sats(tx.fee); + let dust = 547; + + let balance = await g(`balance:${user.id}`); + let ourfee = Math.round(amount * config.fee); + let credit = await g(`credit:${type}:${user.id}`); + let covered = Math.min(credit, ourfee); + ourfee -= covered; + + if (subtract || amount + fee + ourfee > balance) { + if (amount <= fee + ourfee + dust) + fail( + `insufficient funds ⚡️${balance} of ⚡️${amount + fee + ourfee + dust}`, + ); + + outs = [{ [address]: btc(amount - ourfee) }]; + raw = await node.createRawTransaction([], outs, 0, replaceable); + + tx = await node.fundRawTransaction(raw, { + fee_rate: feeRate, + replaceable, + subtractFeeFromOutputs: [0], + }); + + fee = sats(tx.fee); + } + + return { feeRate, ourfee, fee, fees, hex: tx.hex }; +}; + +export let catchUp = async () => { + try { + let txns = []; + for (let [type, n] of Object.entries({ bitcoin: bc, liquid: lq })) { + txns.push( + ...(await n.listTransactions("*", 200)).filter((tx) => { + tx.type = type; + return tx.category === "receive" && tx.confirmations > 0; + }), + ); + } + + for (let { txid, type } of txns) { + try { + if (await db.zScore("seen", txid)) continue; + await got.post(`http://localhost:${process.env.PORT || 3119}/confirm`, { + json: { txid, wallet: config.bitcoin.wallet, type }, + }); + + await db.zAdd("seen", { score: Date.now(), value: txid }); + if ((await db.zCard("seen")) > 10000) + await db.zRemRangeByRank("seen", 0, 0); + } catch (e) { + err("problem confirming", e.message); + } + } + } catch (e) { + err("problem syncing", e.message); + } + + setTimeout(catchUp, 2000); +}; diff --git a/lib/rates.js b/lib/rates.js deleted file mode 100644 index fe942453..00000000 --- a/lib/rates.js +++ /dev/null @@ -1,71 +0,0 @@ -import got from "got"; -import config from "$config"; -import store from "$lib/store"; -import { err, warn } from "$lib/logging"; -import { g, s } from "$lib/db"; -import { sleep } from "$lib/utils"; -import WebSocket from "ws"; - -let last; -let ws; -let connect = async () => { - if (ws && ws.readyState === 1 && Date.now() - last < 5000) return; - if (ws) ws.terminate() && (await sleep(Math.round(Math.random() * 1000))); - - ws = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt@ticker"); - - ws.onmessage = async function (event) { - try { - let msg = JSON.parse(event.data); - let rates = await g("rates"); - let fx = await g("fx"); - if (fx) ({ fx } = fx); - - Object.keys(fx).map((symbol) => { - rates[symbol] = msg.c * fx[symbol]; - }); - - s("rate", msg.c); - s("rates", rates); - last = Date.now(); - } catch (e) { - console.log(e); - err("binance message error", e.message); - } - }; - - ws.onerror = async function (error) { - err("binance socket error", error.message); - }; - - return ws; -}; - -export let getFx = async () => { - connect(); - - let date = 0, - fx = await g("fx"); - if (fx) ({ date, fx } = fx); - - if (Date.now() - date > 24 * 60 * 60 * 1000) { - date = Date.now(); - try { - let r = await got( - `http://data.fixer.io/api/latest?access_key=${config.fixer}`, - ).json(); - let { rates: fx } = r; - let USD = fx["USD"]; - - Object.keys(fx).map((k) => { - fx[k] = fx[k] / USD; - }); - - await s("fx", { date, fx }); - } catch (e) { - err("error fetching rates", e.message); - } - } - - setTimeout(getFx, 30000); -}; diff --git a/lib/rates.js.map b/lib/rates.js.map new file mode 100644 index 00000000..11dc6f05 --- /dev/null +++ b/lib/rates.js.map @@ -0,0 +1 @@ +{"version":3,"file":"rates.js","sourceRoot":"","sources":["rates.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/rates.ts b/lib/rates.ts new file mode 100644 index 00000000..fe942453 --- /dev/null +++ b/lib/rates.ts @@ -0,0 +1,71 @@ +import got from "got"; +import config from "$config"; +import store from "$lib/store"; +import { err, warn } from "$lib/logging"; +import { g, s } from "$lib/db"; +import { sleep } from "$lib/utils"; +import WebSocket from "ws"; + +let last; +let ws; +let connect = async () => { + if (ws && ws.readyState === 1 && Date.now() - last < 5000) return; + if (ws) ws.terminate() && (await sleep(Math.round(Math.random() * 1000))); + + ws = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt@ticker"); + + ws.onmessage = async function (event) { + try { + let msg = JSON.parse(event.data); + let rates = await g("rates"); + let fx = await g("fx"); + if (fx) ({ fx } = fx); + + Object.keys(fx).map((symbol) => { + rates[symbol] = msg.c * fx[symbol]; + }); + + s("rate", msg.c); + s("rates", rates); + last = Date.now(); + } catch (e) { + console.log(e); + err("binance message error", e.message); + } + }; + + ws.onerror = async function (error) { + err("binance socket error", error.message); + }; + + return ws; +}; + +export let getFx = async () => { + connect(); + + let date = 0, + fx = await g("fx"); + if (fx) ({ date, fx } = fx); + + if (Date.now() - date > 24 * 60 * 60 * 1000) { + date = Date.now(); + try { + let r = await got( + `http://data.fixer.io/api/latest?access_key=${config.fixer}`, + ).json(); + let { rates: fx } = r; + let USD = fx["USD"]; + + Object.keys(fx).map((k) => { + fx[k] = fx[k] / USD; + }); + + await s("fx", { date, fx }); + } catch (e) { + err("error fetching rates", e.message); + } + } + + setTimeout(getFx, 30000); +}; diff --git a/lib/register.js b/lib/register.js deleted file mode 100644 index 49111d96..00000000 --- a/lib/register.js +++ /dev/null @@ -1,62 +0,0 @@ -import { got } from "got"; -import { authenticator } from "otplib"; -import { v4 } from "uuid"; - -import config from "$config"; -import countries from "$lib/countries"; -import { s, g, archive } from "$lib/db"; -import { l, warn } from "$lib/logging"; -import { fail, getUser } from "$lib/utils"; - -let valid = /^[\p{L}\p{N}]{2,24}$/u; -export default async (user, ip, requireChallenge) => { - let { profile, password, pubkey, username } = user; - l("registering", username); - - if (!username) fail("Username required"); - username = username.toLowerCase(); - if (!valid.test(username)) - fail("Usernames can only have letters and numbers"); - - let id = v4(); - user.id = id; - - let exists = await getUser(username); - if (exists) fail(`Username ${username} taken`); - - if (password) { - user.password = await Bun.password.hash(password, { - algorithm: "bcrypt", - cost: 1, - }); - } - - user.currency = "USD"; - if (config.ipregistry) { - try { - let { - location: { country: { code } }, - } = await got( - `https://api.ipregistry.co/${ip}?key=${config.ipregistry}&fields=location.country.code`, - ).json(); - - user.currency = countries[code]; - } catch (e) { - warn("unable to detect country from IP", username); - } - } - - user.currencies = [...new Set([user.currency, "CAD", "USD"])]; - user.fiat = false; - user.otpsecret = authenticator.generateSecret(); - user.migrated = true; - user.locktime = 300; - - await s(`user:${id}`, user); - await s(`user:${username}`, id); - await s(`user:${pubkey}`, id); - await s(`balance:${id}`, 0); - - l("new user", username); - return user; -}; diff --git a/lib/register.js.map b/lib/register.js.map new file mode 100644 index 00000000..beb6b80a --- /dev/null +++ b/lib/register.js.map @@ -0,0 +1 @@ +{"version":3,"file":"register.js","sourceRoot":"","sources":["register.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2BAA0B;AAC1B,iCAAuC;AACvC,6BAA0B;AAE1B,oDAA6B;AAC7B,6DAAuC;AACvC,8BAAwC;AACxC,wCAAuC;AACvC,oCAA2C;AAE3C,IAAI,KAAK,GAAG,uBAAuB,CAAC;AACpC,mBAAe,UAAO,IAAI,EAAE,EAAE,EAAE,gBAAgB;;;;;gBACxC,OAAO,GAAiC,IAAI,QAArC,EAAE,QAAQ,GAAuB,IAAI,SAA3B,EAAE,MAAM,GAAe,IAAI,OAAnB,EAAE,QAAQ,GAAK,IAAI,SAAT,CAAU;gBACnD,IAAA,WAAC,EAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;gBAE3B,IAAI,CAAC,QAAQ;oBAAE,IAAA,YAAI,EAAC,mBAAmB,CAAC,CAAC;gBACzC,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;gBAClC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;oBACvB,IAAA,YAAI,EAAC,6CAA6C,CAAC,CAAC;gBAElD,EAAE,GAAG,IAAA,SAAE,GAAE,CAAC;gBACd,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;gBAEA,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;gBAAhC,MAAM,GAAG,SAAuB;gBACpC,IAAI,MAAM;oBAAE,IAAA,YAAI,EAAC,mBAAY,QAAQ,WAAQ,CAAC,CAAC;qBAE3C,QAAQ,EAAR,wBAAQ;gBACV,KAAA,IAAI,CAAA;gBAAY,qBAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE;wBAChD,SAAS,EAAE,QAAQ;wBACnB,IAAI,EAAE,CAAC;qBACR,CAAC,EAAA;;gBAHF,GAAK,QAAQ,GAAG,SAGd,CAAC;;;gBAGL,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;qBAClB,iBAAM,CAAC,UAAU,EAAjB,wBAAiB;;;;gBAIb,qBAAM,IAAA,SAAG,EACX,oCAA6B,EAAE,kBAAQ,iBAAM,CAAC,UAAU,kCAA+B,CACxF,CAAC,IAAI,EAAE,EAAA;;gBAHiB,IAAI,GACzB,CAAA,SAEI,CAAA,sBAHqB;gBAK7B,IAAI,CAAC,QAAQ,GAAG,mBAAS,CAAC,IAAI,CAAC,CAAC;;;;gBAEhC,IAAA,cAAI,EAAC,kCAAkC,EAAE,QAAQ,CAAC,CAAC;;;gBAIvD,IAAI,CAAC,UAAU,qBAAO,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,OAAC,CAAC;gBAC9D,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;gBAClB,IAAI,CAAC,SAAS,GAAG,sBAAa,CAAC,cAAc,EAAE,CAAC;gBAChD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;gBAEpB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;gBAA3B,SAA2B,CAAC;gBAC5B,qBAAM,IAAA,MAAC,EAAC,eAAQ,QAAQ,CAAE,EAAE,EAAE,CAAC,EAAA;;gBAA/B,SAA+B,CAAC;gBAChC,qBAAM,IAAA,MAAC,EAAC,eAAQ,MAAM,CAAE,EAAE,EAAE,CAAC,EAAA;;gBAA7B,SAA6B,CAAC;gBAC9B,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,EAAE,CAAC,CAAC,EAAA;;gBAA3B,SAA2B,CAAC;gBAE5B,IAAA,WAAC,EAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;gBACxB,sBAAO,IAAI,EAAC;;;KACb,EAAC"} \ No newline at end of file diff --git a/lib/register.ts b/lib/register.ts new file mode 100644 index 00000000..49111d96 --- /dev/null +++ b/lib/register.ts @@ -0,0 +1,62 @@ +import { got } from "got"; +import { authenticator } from "otplib"; +import { v4 } from "uuid"; + +import config from "$config"; +import countries from "$lib/countries"; +import { s, g, archive } from "$lib/db"; +import { l, warn } from "$lib/logging"; +import { fail, getUser } from "$lib/utils"; + +let valid = /^[\p{L}\p{N}]{2,24}$/u; +export default async (user, ip, requireChallenge) => { + let { profile, password, pubkey, username } = user; + l("registering", username); + + if (!username) fail("Username required"); + username = username.toLowerCase(); + if (!valid.test(username)) + fail("Usernames can only have letters and numbers"); + + let id = v4(); + user.id = id; + + let exists = await getUser(username); + if (exists) fail(`Username ${username} taken`); + + if (password) { + user.password = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 1, + }); + } + + user.currency = "USD"; + if (config.ipregistry) { + try { + let { + location: { country: { code } }, + } = await got( + `https://api.ipregistry.co/${ip}?key=${config.ipregistry}&fields=location.country.code`, + ).json(); + + user.currency = countries[code]; + } catch (e) { + warn("unable to detect country from IP", username); + } + } + + user.currencies = [...new Set([user.currency, "CAD", "USD"])]; + user.fiat = false; + user.otpsecret = authenticator.generateSecret(); + user.migrated = true; + user.locktime = 300; + + await s(`user:${id}`, user); + await s(`user:${username}`, id); + await s(`user:${pubkey}`, id); + await s(`balance:${id}`, 0); + + l("new user", username); + return user; +}; diff --git a/lib/rpc.js b/lib/rpc.js deleted file mode 100644 index bb57d075..00000000 --- a/lib/rpc.js +++ /dev/null @@ -1,28 +0,0 @@ -import got from "got"; - -export default ({ host, port, wallet, username, password }) => - new Proxy( - {}, - { - get: - (target, prop) => - (...params) => - ((method, ...params) => { - let url = `http://${host}:${port}/wallet/${wallet}`; - return got - .post(url, { - json: { - method, - params, - }, - headers: { - authorization: `Basic ${Buffer.from( - `${username}:${password}`, - ).toString("base64")}`, - }, - }) - .json() - .then(({ result }) => result); - })(prop.toLowerCase(), ...params), - }, - ); diff --git a/lib/rpc.js.map b/lib/rpc.js.map new file mode 100644 index 00000000..d3e67844 --- /dev/null +++ b/lib/rpc.js.map @@ -0,0 +1 @@ +{"version":3,"file":"rpc.js","sourceRoot":"","sources":["rpc.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,4CAAsB;AAEtB,mBAAe,UAAC,EAA0C;QAAxC,IAAI,UAAA,EAAE,IAAI,UAAA,EAAE,MAAM,YAAA,EAAE,QAAQ,cAAA,EAAE,QAAQ,cAAA;IACtD,OAAA,IAAI,KAAK,CACP,EAAE,EACF;QACE,GAAG,EACD,UAAC,MAAM,EAAE,IAAI;YACb,OAAA;gBAAC,gBAAS;qBAAT,UAAS,EAAT,qBAAS,EAAT,IAAS;oBAAT,2BAAS;;gBACR,OAAA,CAAC,UAAC,MAAM;oBAAE,gBAAS;yBAAT,UAAS,EAAT,qBAAS,EAAT,IAAS;wBAAT,+BAAS;;oBACjB,IAAI,GAAG,GAAG,iBAAU,IAAI,cAAI,IAAI,qBAAW,MAAM,CAAE,CAAC;oBACpD,OAAO,aAAG;yBACP,IAAI,CAAC,GAAG,EAAE;wBACT,IAAI,EAAE;4BACJ,MAAM,QAAA;4BACN,MAAM,QAAA;yBACP;wBACD,OAAO,EAAE;4BACP,aAAa,EAAE,gBAAS,MAAM,CAAC,IAAI,CACjC,UAAG,QAAQ,cAAI,QAAQ,CAAE,CAC1B,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAE;yBACvB;qBACF,CAAC;yBACD,IAAI,EAAE;yBACN,IAAI,CAAC,UAAC,EAAU;4BAAR,MAAM,YAAA;wBAAO,OAAA,MAAM;oBAAN,CAAM,CAAC,CAAC;gBAClC,CAAC,CAAC,8BAAC,IAAI,CAAC,WAAW,EAAE,GAAK,MAAM;YAhBhC,CAgBiC;QAjBnC,CAiBmC;KACtC,CACF;AAxBD,CAwBC,EAAC"} \ No newline at end of file diff --git a/lib/rpc.ts b/lib/rpc.ts new file mode 100644 index 00000000..bb57d075 --- /dev/null +++ b/lib/rpc.ts @@ -0,0 +1,28 @@ +import got from "got"; + +export default ({ host, port, wallet, username, password }) => + new Proxy( + {}, + { + get: + (target, prop) => + (...params) => + ((method, ...params) => { + let url = `http://${host}:${port}/wallet/${wallet}`; + return got + .post(url, { + json: { + method, + params, + }, + headers: { + authorization: `Basic ${Buffer.from( + `${username}:${password}`, + ).toString("base64")}`, + }, + }) + .json() + .then(({ result }) => result); + })(prop.toLowerCase(), ...params), + }, + ); diff --git a/lib/sockets.js b/lib/sockets.js deleted file mode 100644 index b1fc719c..00000000 --- a/lib/sockets.js +++ /dev/null @@ -1,165 +0,0 @@ -import config from "$config"; -import store from "$lib/store"; -import jwt from "jsonwebtoken"; -import { v4 } from "uuid"; -import whitelist from "$lib/whitelist"; -import { l, err, warn } from "$lib/logging"; -import { g } from "$lib/db"; -import { fail, getUser, pick } from "$lib/utils"; - -const code = 1000; -const all = {}; -const subscriptions = []; -const lastSeen = {}; -const users = {}; - -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 && s.invoice.hash === data.hash) { - s.ws.send(JSON.stringify({ type, data })); - } - } - } - - 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 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); - } - } - - Object.values(all).map((ws) => { - if (!sent.includes(ws.id)) ws.send(JSON.stringify({ type, data })); - }); -}; - -let track = async (ws, token) => { - let { id } = ws; - let { id: uid } = jwt.decode(token); - - if (!uid) fail("Invalid JWT token"); - let user = await getUser(uid); - if (!user) fail(`User not found ${uid}`); - - if (!store.sockets[uid]) store.sockets[uid] = {}; - - const existing = Object.keys(store.sockets[uid]); - if (existing.length > 4) { - const p = existing.find((sid) => sid !== uid); - store.sockets[uid][p].close(code, "too many sockets"); - } - - store.sockets[uid][id] = ws; - users[id] = uid; - ws.user = user; -}; - -console.log("starting socket server"); -Bun.serve({ - hostname: "0.0.0.0", - port: 3120, - fetch(req, server) { - console.log("here we go"); - // upgrade the request to a WebSocket - if (server.upgrade(req)) { - console.log("UPGRADING"); - return; // do not return a Response - } - return new Response("Upgrade failed", { status: 500 }); - }, - websocket: { - async message(ws, message) { - let type, data; - - try { - ({ type, data } = JSON.parse(message)); - } catch (e) { - err("coudn't parse socket message"); - } - - switch (type) { - case "heartbeat": - ws.beats = 0; - try { - if (data) await track(ws, data); - ws.send(JSON.stringify({ type: "id", data: ws.id })); - } catch (e) { - // err("Failed to send heartbeat", e.message); - } - break; - - case "login": - try { - if (!data || data === "null" || !jwt.decode(data)) - return ws.close(code, `bad token ${data}`); - - await track(ws, data); - } catch (e) { - if (e.message.includes("not found")) - ws.send(JSON.stringify({ type: "logout" })); - setTimeout( - () => ws && ws.close(code, `closing due to error ${e.message}`), - 1000, - ); - } - break; - case "token": - if (!ws.user) return; - ws.token = data; - break; - case "lnurl": - store.sessions[data] = ws; - break; - case "subscribe": - subscriptions.push({ invoice: data, ws }); - break; - default: - warn("received socket message of unknown type", type, data); - } - }, - open(ws) { - console.log("OPENING"); - const id = v4(); - ws.id = id; - ws.beats = 0; - ws.send(JSON.stringify({ type: "connected", data: id })); - all[id] = ws; - }, - close(ws) { - let { id } = ws; - try { - const uid = users[id]; - if (store.sockets[uid] && store.sockets[uid][id]) { - delete store.sockets[uid][id]; - } - - if (all[id]) delete all[id]; - } catch (e) { - err("problem closing socket", e.message); - } - }, - }, -}); - -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, `${uid} ${id} lost heartbeat`); - } - } -}; diff --git a/lib/sockets.js.map b/lib/sockets.js.map new file mode 100644 index 00000000..8fb722a2 --- /dev/null +++ b/lib/sockets.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sockets.js","sourceRoot":"","sources":["sockets.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/sockets.ts b/lib/sockets.ts new file mode 100644 index 00000000..0391a07a --- /dev/null +++ b/lib/sockets.ts @@ -0,0 +1,161 @@ +import config from "$config"; +import store from "$lib/store"; +import jwt from "jsonwebtoken"; +import { v4 } from "uuid"; +import whitelist from "$lib/whitelist"; +import { l, err, warn } from "$lib/logging"; +import { g } from "$lib/db"; +import { fail, getUser, pick } from "$lib/utils"; + +const code = 1000; +const all = {}; +const subscriptions = []; +const lastSeen = {}; +const users = {}; + +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 && s.invoice.hash === data.hash) { + s.ws.send(JSON.stringify({ type, data })); + } + } + } + + 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 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); + } + } + + Object.values(all).map((ws) => { + if (!sent.includes(ws.id)) ws.send(JSON.stringify({ type, data })); + }); +}; + +let track = async (ws, token) => { + let { id } = ws; + let { id: uid } = jwt.decode(token); + + if (!uid) fail("Invalid JWT token"); + let user = await getUser(uid); + if (!user) fail(`User not found ${uid}`); + + if (!store.sockets[uid]) store.sockets[uid] = {}; + + const existing = Object.keys(store.sockets[uid]); + if (existing.length > 4) { + const p = existing.find((sid) => sid !== uid); + store.sockets[uid][p].close(code, "too many sockets"); + } + + store.sockets[uid][id] = ws; + users[id] = uid; + ws.user = user; +}; + +Bun.serve({ + hostname: "0.0.0.0", + port: 3120, + fetch(req, server) { + // upgrade the request to a WebSocket + if (server.upgrade(req)) { + return; // do not return a Response + } + return new Response("Upgrade failed", { status: 500 }); + }, + websocket: { + async message(ws, message) { + let type, data; + + try { + ({ type, data } = JSON.parse(message)); + } catch (e) { + err("coudn't parse socket message"); + } + + switch (type) { + case "heartbeat": + ws.beats = 0; + try { + if (data) await track(ws, data); + ws.send(JSON.stringify({ type: "id", data: ws.id })); + } catch (e) { + // err("Failed to send heartbeat", e.message); + } + break; + + case "login": + try { + if (!data || data === "null" || !jwt.decode(data)) + return ws.close(code, `bad token ${data}`); + + await track(ws, data); + } catch (e) { + if (e.message.includes("not found")) + ws.send(JSON.stringify({ type: "logout" })); + setTimeout( + () => ws && ws.close(code, `closing due to error ${e.message}`), + 1000, + ); + } + break; + case "token": + if (!ws.user) return; + ws.token = data; + break; + case "lnurl": + store.sessions[data] = ws; + break; + case "subscribe": + subscriptions.push({ invoice: data, ws }); + break; + default: + warn("received socket message of unknown type", type, data); + } + }, + open(ws) { + const id = v4(); + ws.id = id; + ws.beats = 0; + ws.send(JSON.stringify({ type: "connected", data: id })); + all[id] = ws; + }, + close(ws) { + let { id } = ws; + try { + const uid = users[id]; + if (store.sockets[uid] && store.sockets[uid][id]) { + delete store.sockets[uid][id]; + } + + if (all[id]) delete all[id]; + } catch (e) { + err("problem closing socket", e.message); + } + }, + }, +}); + +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, `${uid} ${id} lost heartbeat`); + } + } +}; diff --git a/lib/store.js b/lib/store.js deleted file mode 100644 index 4c86761c..00000000 --- a/lib/store.js +++ /dev/null @@ -1,22 +0,0 @@ -export default { - addresses: {}, - challenge: {}, - change: [], - issuances: {}, - logins: {}, - nodes: [], - seen: [], - sessions: {}, - sockets: {}, - unaccounted: [], - networks: [], - rates: { USD: 20000 }, - fx: {}, - assets: {}, - bcAddressIndex: 0, - lqAddressIndex: 0, - last: {}, - payments: [], - fetching: {}, - timeouts: {}, -}; diff --git a/lib/store.js.map b/lib/store.js.map new file mode 100644 index 00000000..f576e0c0 --- /dev/null +++ b/lib/store.js.map @@ -0,0 +1 @@ +{"version":3,"file":"store.js","sourceRoot":"","sources":["store.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,SAAS,EAAE,EAAE;IACb,SAAS,EAAE,EAAE;IACb,MAAM,EAAE,EAAE;IACV,SAAS,EAAE,EAAE;IACb,MAAM,EAAE,EAAE;IACV,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,EAAE;IACZ,OAAO,EAAE,EAAE;IACX,WAAW,EAAE,EAAE;IACf,QAAQ,EAAE,EAAE;IACZ,KAAK,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE;IACrB,EAAE,EAAE,EAAE;IACN,MAAM,EAAE,EAAE;IACV,cAAc,EAAE,CAAC;IACjB,cAAc,EAAE,CAAC;IACjB,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,EAAE;IACZ,QAAQ,EAAE,EAAE;IACZ,QAAQ,EAAE,EAAE;CACb,CAAC"} \ No newline at end of file diff --git a/lib/store.ts b/lib/store.ts new file mode 100644 index 00000000..4c86761c --- /dev/null +++ b/lib/store.ts @@ -0,0 +1,22 @@ +export default { + addresses: {}, + challenge: {}, + change: [], + issuances: {}, + logins: {}, + nodes: [], + seen: [], + sessions: {}, + sockets: {}, + unaccounted: [], + networks: [], + rates: { USD: 20000 }, + fx: {}, + assets: {}, + bcAddressIndex: 0, + lqAddressIndex: 0, + last: {}, + payments: [], + fetching: {}, + timeouts: {}, +}; diff --git a/lib/upload.js b/lib/upload.js deleted file mode 100644 index 0fb31ea5..00000000 --- a/lib/upload.js +++ /dev/null @@ -1,70 +0,0 @@ -import { fileTypeFromStream } from "file-type"; -import pump from "pump"; -import Clone from "readable-stream-clone"; -import fs from "fs"; -import path from "path"; -import { pipeline } from "stream"; -import { g, s, db } from "$lib/db"; -import { bail, fail } from "$lib/utils"; -import crypto from "crypto"; -import { err } from "$lib/logging"; - -async function getSha256Hash(stream) { - return new Promise((resolve, reject) => { - const hash = crypto.createHash("sha256"); - stream.pipe(hash); - - let hashValue; - hash.on("data", (data) => { - hashValue = data.toString("hex"); - }); - - hash.on("end", () => { - resolve(hashValue); - }); - - hash.on("error", reject); - }); -} - -export default async (req, res) => { - try { - let { - params: { type }, - } = req; - - let width = type === "banner" ? 1920 : 240; - - let data = await req.file(); - let s1 = new Clone(data.file); - let s2 = new Clone(data.file); - - let [format, ext] = (await fileTypeFromStream(s1)).mime.split("/"); - - if (format !== "image" && !["jpg", "jpeg", "png"].includes(ext)) - fail("unsupported file type"); - - let processedBuffer = await streamToBuffer(s2.pipe(t)); - let hash = crypto - .createHash("sha256") - .update(processedBuffer) - .digest("hex"); - - let filePath = `/home/bun/app/data/uploads/${hash}.webp`; - fs.writeFileSync(filePath, processedBuffer); - - res.send({ hash }); - } catch (e) { - err("problem uploading", e.message); - bail(res, e.message); - } -}; - -function streamToBuffer(stream) { - return new Promise((resolve, reject) => { - const chunks = []; - stream.on("data", (chunk) => chunks.push(chunk)); - stream.on("end", () => resolve(Buffer.concat(chunks))); - stream.on("error", reject); - }); -} diff --git a/lib/upload.js.map b/lib/upload.js.map new file mode 100644 index 00000000..9f49b99c --- /dev/null +++ b/lib/upload.js.map @@ -0,0 +1 @@ +{"version":3,"file":"upload.js","sourceRoot":"","sources":["upload.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/upload.ts b/lib/upload.ts new file mode 100644 index 00000000..0fb31ea5 --- /dev/null +++ b/lib/upload.ts @@ -0,0 +1,70 @@ +import { fileTypeFromStream } from "file-type"; +import pump from "pump"; +import Clone from "readable-stream-clone"; +import fs from "fs"; +import path from "path"; +import { pipeline } from "stream"; +import { g, s, db } from "$lib/db"; +import { bail, fail } from "$lib/utils"; +import crypto from "crypto"; +import { err } from "$lib/logging"; + +async function getSha256Hash(stream) { + return new Promise((resolve, reject) => { + const hash = crypto.createHash("sha256"); + stream.pipe(hash); + + let hashValue; + hash.on("data", (data) => { + hashValue = data.toString("hex"); + }); + + hash.on("end", () => { + resolve(hashValue); + }); + + hash.on("error", reject); + }); +} + +export default async (req, res) => { + try { + let { + params: { type }, + } = req; + + let width = type === "banner" ? 1920 : 240; + + let data = await req.file(); + let s1 = new Clone(data.file); + let s2 = new Clone(data.file); + + let [format, ext] = (await fileTypeFromStream(s1)).mime.split("/"); + + if (format !== "image" && !["jpg", "jpeg", "png"].includes(ext)) + fail("unsupported file type"); + + let processedBuffer = await streamToBuffer(s2.pipe(t)); + let hash = crypto + .createHash("sha256") + .update(processedBuffer) + .digest("hex"); + + let filePath = `/home/bun/app/data/uploads/${hash}.webp`; + fs.writeFileSync(filePath, processedBuffer); + + res.send({ hash }); + } catch (e) { + err("problem uploading", e.message); + bail(res, e.message); + } +}; + +function streamToBuffer(stream) { + return new Promise((resolve, reject) => { + const chunks = []; + stream.on("data", (chunk) => chunks.push(chunk)); + stream.on("end", () => resolve(Buffer.concat(chunks))); + stream.on("error", reject); + }); +} diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index ada32521..00000000 --- a/lib/utils.js +++ /dev/null @@ -1,160 +0,0 @@ -import { randomBytes, bytesToHex } from "@noble/hashes/utils"; -import migrate from "$lib/migrate"; -import store from "$lib/store"; -import { g, s } from "$lib/db"; -import config from "$config"; - -export let fail = (msg) => { - throw new Error(msg); -}; - -export let nada = () => {}; - -export let sleep = (n) => new Promise((r) => setTimeout(r, n)); -export let wait = async (f, s = 300, n = 50) => { - let i = 0; - while (!(await f()) && i < s) (await sleep(n)) && i++; - if (i >= s) fail("timeout"); - return f(); -}; - -export let prod = process.env.NODE_ENV === "production"; - -export let bail = (res, msg) => res.code(500).send(msg); - -export let SATS = 100000000; -export let sats = (n) => Math.round(n * SATS); -export let btc = (n) => parseFloat((n / SATS).toFixed(8)); -export let fiat = (n, r) => (n * r) / SATS; - -export let uniq = (a, k) => [...new Map(a.map((x) => [k(x), x])).values()]; -export let pick = (O, K) => - K.reduce((o, k) => (typeof O[k] !== "undefined" && (o[k] = O[k]), o), {}); - -export let bip21 = (address, { amount, memo, tip, type }) => { - if (!(amount || memo)) return address; - - let network = { liquid: "liquidnetwork", bitcoin: "bitcoin" }[type]; - let url = new URLSearchParams(); - - if (amount) { - url.append("amount", btc(amount + tip)); - if (type === "liquid") url.append("assetid", config.liquid.btc); - } - - if (memo) url.append("memo", memo); - - return `${network}:${address}?${url.toString()}`; -}; - -export let fields = [ - "cipher", - "pubkey", - "password", - "username", - "salt", - "currency", - "currencies", - "fiat", - "otpsecret", - "balance", - "ip", - "pin", - "display", - "haspin", - "profile", - "prompt", - "banner", -]; - -export let getUser = async (username) => { - if (username === "undefined") fail("invalid user"); - let user = await migrate(username); - if (user && !user.nwc) { - user.nwc = bytesToHex(randomBytes(32)); - s(`user:${user.id}`, user); - } - - return user; -}; - -export let getInvoice = async (hash) => { - let iid = await g(`invoice:${hash}`); - if (iid && iid.id) iid = iid.id; - else if (iid && iid.hash) iid = iid.hash; - return await g(`invoice:${iid}`); -}; - -export let f = (s, currency) => - new Intl.NumberFormat("en-US", { - style: "currency", - currency, - }) - .format(s) - .replace("CA", ""); - -export function formatReceipt(items, currency) { - function wrapText(text, maxWidth) { - const words = text.split(" "); - const lines = []; - let currentLine = ""; - words.forEach((word) => { - if (currentLine.length + word.length + 1 > maxWidth) { - lines.push(currentLine); - currentLine = word; - } else { - currentLine += (currentLine.length > 0 ? " " : "") + word; - } - }); - if (currentLine) lines.push(currentLine); - return lines; - } - - function calculateColumnWidths(items) { - let maxQuantityLength = 0; - let maxPriceLength = 0; - - items.forEach((item) => { - const quantityLength = String(item.quantity).length; - if (quantityLength > maxQuantityLength) { - maxQuantityLength = quantityLength; - } - - const priceLength = f(item.price * item.quantity, currency).length; - if (priceLength > maxPriceLength) { - maxPriceLength = priceLength; - } - }); - - // Add padding to the widths for aesthetic spacing - return { - quantityColumnWidth: maxQuantityLength + 1, // Space after quantity - priceColumnWidth: maxPriceLength + 1, // Space before price - }; - } - const maxLineWidth = 32; - const { quantityColumnWidth, priceColumnWidth } = - calculateColumnWidths(items); - const nameColumnWidth = maxLineWidth - quantityColumnWidth - priceColumnWidth; - - return items - .map((item) => { - const quantityStr = String(item.quantity).padEnd(quantityColumnWidth); - const priceStr = f(item.price * item.quantity, currency).padStart( - priceColumnWidth, - ); - const nameLines = wrapText(item.name, nameColumnWidth); - - // Construct the full line(s) with the first line including the price - let fullLines = [ - `${quantityStr}${nameLines[0].padEnd(nameColumnWidth)}${priceStr}`, - ]; - // Add any additional name lines, properly indented - for (let i = 1; i < nameLines.length; i++) { - fullLines.push(" ".repeat(quantityColumnWidth) + nameLines[i]); - } - - return fullLines.join("\n"); - }) - .join("\n"); -} diff --git a/lib/utils.js.map b/lib/utils.js.map new file mode 100644 index 00000000..1652f149 --- /dev/null +++ b/lib/utils.js.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.js","sourceRoot":"","sources":["utils.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 00000000..ada32521 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,160 @@ +import { randomBytes, bytesToHex } from "@noble/hashes/utils"; +import migrate from "$lib/migrate"; +import store from "$lib/store"; +import { g, s } from "$lib/db"; +import config from "$config"; + +export let fail = (msg) => { + throw new Error(msg); +}; + +export let nada = () => {}; + +export let sleep = (n) => new Promise((r) => setTimeout(r, n)); +export let wait = async (f, s = 300, n = 50) => { + let i = 0; + while (!(await f()) && i < s) (await sleep(n)) && i++; + if (i >= s) fail("timeout"); + return f(); +}; + +export let prod = process.env.NODE_ENV === "production"; + +export let bail = (res, msg) => res.code(500).send(msg); + +export let SATS = 100000000; +export let sats = (n) => Math.round(n * SATS); +export let btc = (n) => parseFloat((n / SATS).toFixed(8)); +export let fiat = (n, r) => (n * r) / SATS; + +export let uniq = (a, k) => [...new Map(a.map((x) => [k(x), x])).values()]; +export let pick = (O, K) => + K.reduce((o, k) => (typeof O[k] !== "undefined" && (o[k] = O[k]), o), {}); + +export let bip21 = (address, { amount, memo, tip, type }) => { + if (!(amount || memo)) return address; + + let network = { liquid: "liquidnetwork", bitcoin: "bitcoin" }[type]; + let url = new URLSearchParams(); + + if (amount) { + url.append("amount", btc(amount + tip)); + if (type === "liquid") url.append("assetid", config.liquid.btc); + } + + if (memo) url.append("memo", memo); + + return `${network}:${address}?${url.toString()}`; +}; + +export let fields = [ + "cipher", + "pubkey", + "password", + "username", + "salt", + "currency", + "currencies", + "fiat", + "otpsecret", + "balance", + "ip", + "pin", + "display", + "haspin", + "profile", + "prompt", + "banner", +]; + +export let getUser = async (username) => { + if (username === "undefined") fail("invalid user"); + let user = await migrate(username); + if (user && !user.nwc) { + user.nwc = bytesToHex(randomBytes(32)); + s(`user:${user.id}`, user); + } + + return user; +}; + +export let getInvoice = async (hash) => { + let iid = await g(`invoice:${hash}`); + if (iid && iid.id) iid = iid.id; + else if (iid && iid.hash) iid = iid.hash; + return await g(`invoice:${iid}`); +}; + +export let f = (s, currency) => + new Intl.NumberFormat("en-US", { + style: "currency", + currency, + }) + .format(s) + .replace("CA", ""); + +export function formatReceipt(items, currency) { + function wrapText(text, maxWidth) { + const words = text.split(" "); + const lines = []; + let currentLine = ""; + words.forEach((word) => { + if (currentLine.length + word.length + 1 > maxWidth) { + lines.push(currentLine); + currentLine = word; + } else { + currentLine += (currentLine.length > 0 ? " " : "") + word; + } + }); + if (currentLine) lines.push(currentLine); + return lines; + } + + function calculateColumnWidths(items) { + let maxQuantityLength = 0; + let maxPriceLength = 0; + + items.forEach((item) => { + const quantityLength = String(item.quantity).length; + if (quantityLength > maxQuantityLength) { + maxQuantityLength = quantityLength; + } + + const priceLength = f(item.price * item.quantity, currency).length; + if (priceLength > maxPriceLength) { + maxPriceLength = priceLength; + } + }); + + // Add padding to the widths for aesthetic spacing + return { + quantityColumnWidth: maxQuantityLength + 1, // Space after quantity + priceColumnWidth: maxPriceLength + 1, // Space before price + }; + } + const maxLineWidth = 32; + const { quantityColumnWidth, priceColumnWidth } = + calculateColumnWidths(items); + const nameColumnWidth = maxLineWidth - quantityColumnWidth - priceColumnWidth; + + return items + .map((item) => { + const quantityStr = String(item.quantity).padEnd(quantityColumnWidth); + const priceStr = f(item.price * item.quantity, currency).padStart( + priceColumnWidth, + ); + const nameLines = wrapText(item.name, nameColumnWidth); + + // Construct the full line(s) with the first line including the price + let fullLines = [ + `${quantityStr}${nameLines[0].padEnd(nameColumnWidth)}${priceStr}`, + ]; + // Add any additional name lines, properly indented + for (let i = 1; i < nameLines.length; i++) { + fullLines.push(" ".repeat(quantityColumnWidth) + nameLines[i]); + } + + return fullLines.join("\n"); + }) + .join("\n"); +} diff --git a/lib/webhooks.js b/lib/webhooks.js deleted file mode 100644 index b1f035a4..00000000 --- a/lib/webhooks.js +++ /dev/null @@ -1,33 +0,0 @@ -import config from "$config"; -import got from "got"; -import { l, err } from "$lib/logging"; - -export const callWebhook = async (invoice, payment) => { - try { - if (!invoice || !payment) return; - - let { address, received, text, webhook, secret } = invoice; - - if (webhook) { - let { amount, confirmed, hash, memo } = payment; - - l("calling webhook", webhook, amount, hash, address, text); - let res = await got.post(webhook, { - json: { - address, - amount, - confirmed, - hash, - memo, - received, - text, - secret, - }, - https: { rejectUnauthorized: false } - }); - return res; - } - } catch (e) { - err("problem calling webhook", e.message); - } -}; diff --git a/lib/webhooks.js.map b/lib/webhooks.js.map new file mode 100644 index 00000000..40906dcd --- /dev/null +++ b/lib/webhooks.js.map @@ -0,0 +1 @@ +{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["webhooks.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,4CAAsB;AACtB,wCAAsC;AAE/B,IAAM,WAAW,GAAG,UAAO,OAAO,EAAE,OAAO;;;;;;gBAE9C,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO;oBAAE,sBAAO;gBAE3B,OAAO,GAAsC,OAAO,QAA7C,EAAE,QAAQ,GAA4B,OAAO,SAAnC,EAAE,IAAI,GAAsB,OAAO,KAA7B,EAAE,OAAO,GAAa,OAAO,QAApB,EAAE,MAAM,GAAK,OAAO,OAAZ,CAAa;qBAEvD,OAAO,EAAP,wBAAO;gBACH,MAAM,GAA4B,OAAO,OAAnC,EAAE,SAAS,GAAiB,OAAO,UAAxB,EAAE,IAAI,GAAW,OAAO,KAAlB,EAAE,IAAI,GAAK,OAAO,KAAZ,CAAa;gBAEhD,IAAA,WAAC,EAAC,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;gBACjD,qBAAM,aAAG,CAAC,IAAI,CAAC,OAAO,EAAE;wBAChC,IAAI,EAAE;4BACJ,OAAO,SAAA;4BACP,MAAM,QAAA;4BACN,SAAS,WAAA;4BACT,IAAI,MAAA;4BACJ,IAAI,MAAA;4BACJ,QAAQ,UAAA;4BACR,IAAI,MAAA;4BACJ,MAAM,QAAA;yBACP;wBACC,KAAK,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE;qBACvC,CAAC,EAAA;;gBAZE,GAAG,GAAG,SAYR;gBACF,sBAAO,GAAG,EAAC;;;;gBAGb,IAAA,aAAG,EAAC,yBAAyB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;KAE7C,CAAC;AA5BW,QAAA,WAAW,eA4BtB"} \ No newline at end of file diff --git a/lib/webhooks.ts b/lib/webhooks.ts new file mode 100644 index 00000000..b1f035a4 --- /dev/null +++ b/lib/webhooks.ts @@ -0,0 +1,33 @@ +import config from "$config"; +import got from "got"; +import { l, err } from "$lib/logging"; + +export const callWebhook = async (invoice, payment) => { + try { + if (!invoice || !payment) return; + + let { address, received, text, webhook, secret } = invoice; + + if (webhook) { + let { amount, confirmed, hash, memo } = payment; + + l("calling webhook", webhook, amount, hash, address, text); + let res = await got.post(webhook, { + json: { + address, + amount, + confirmed, + hash, + memo, + received, + text, + secret, + }, + https: { rejectUnauthorized: false } + }); + return res; + } + } catch (e) { + err("problem calling webhook", e.message); + } +}; diff --git a/lib/whitelist.js b/lib/whitelist.js deleted file mode 100644 index bf5b4eb9..00000000 --- a/lib/whitelist.js +++ /dev/null @@ -1,45 +0,0 @@ -export default [ - "address", - "admin", - "anon", - "autowithdraw", - "balance", - "banner", - "cipher", - "currencies", - "currency", - "destination", - "display", - "email", - "fiat", - "followers", - "follows", - "haspin", - "hasprinter", - "hidepay", - "id", - "index", - "keys", - "language", - "linked", - "locktime", - "memoPrompt", - "nip5", - "notify", - "nsec", - "nwc", - "payments", - "profile", - "prompt", - "pubkey", - "reserve", - "salt", - "seed", - "shopifyStore", - "shopifyToken", - "theme", - "threshold", - "twofa", - "username", - "verified", -]; diff --git a/lib/whitelist.js.map b/lib/whitelist.js.map new file mode 100644 index 00000000..f1e01c42 --- /dev/null +++ b/lib/whitelist.js.map @@ -0,0 +1 @@ +{"version":3,"file":"whitelist.js","sourceRoot":"","sources":["whitelist.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,SAAS;IACT,OAAO;IACP,MAAM;IACN,cAAc;IACd,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,YAAY;IACZ,UAAU;IACV,aAAa;IACb,SAAS;IACT,OAAO;IACP,MAAM;IACN,WAAW;IACX,SAAS;IACT,QAAQ;IACR,YAAY;IACZ,SAAS;IACT,IAAI;IACJ,OAAO;IACP,MAAM;IACN,UAAU;IACV,QAAQ;IACR,UAAU;IACV,YAAY;IACZ,MAAM;IACN,QAAQ;IACR,MAAM;IACN,KAAK;IACL,UAAU;IACV,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,MAAM;IACN,MAAM;IACN,cAAc;IACd,cAAc;IACd,OAAO;IACP,WAAW;IACX,OAAO;IACP,UAAU;IACV,UAAU;CACX,CAAC"} \ No newline at end of file diff --git a/lib/whitelist.ts b/lib/whitelist.ts new file mode 100644 index 00000000..bf5b4eb9 --- /dev/null +++ b/lib/whitelist.ts @@ -0,0 +1,45 @@ +export default [ + "address", + "admin", + "anon", + "autowithdraw", + "balance", + "banner", + "cipher", + "currencies", + "currency", + "destination", + "display", + "email", + "fiat", + "followers", + "follows", + "haspin", + "hasprinter", + "hidepay", + "id", + "index", + "keys", + "language", + "linked", + "locktime", + "memoPrompt", + "nip5", + "notify", + "nsec", + "nwc", + "payments", + "profile", + "prompt", + "pubkey", + "reserve", + "salt", + "seed", + "shopifyStore", + "shopifyToken", + "theme", + "threshold", + "twofa", + "username", + "verified", +]; diff --git a/loader.js b/loader.js deleted file mode 100644 index b15c5d6e..00000000 --- a/loader.js +++ /dev/null @@ -1,22 +0,0 @@ -import path from "path"; -import fs from "fs"; - -export function resolve(specifier, context, nextResolve) { - const { parentURL = null } = context; - - if (specifier.startsWith("$")) { - if (specifier.startsWith("$config")) { - specifier = "file://" + path.resolve(".") + "/config.js"; - } else if (specifier.startsWith("$")) { - specifier = - specifier.replace(/^\$/, "file://" + path.resolve(".") + "/") + ".js"; - } - - return { - shortCircuit: true, - url: new URL(specifier).href, - }; - } - - return nextResolve(specifier, context); -} diff --git a/package.json b/package.json index 4b43a1cd..a4e70719 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,9 @@ }, "devDependencies": { "@biomejs/biome": "^1.4.1", - "nodemon": "^2.0.7" + "@types/bun": "^1.1.8", + "nodemon": "^2.0.7", + "typescript": "^5.5.4" }, "apidoc": { "title": "CoinOS Server API Documentation", diff --git a/routes/ecash.js b/routes/ecash.js deleted file mode 100644 index 8c2684b1..00000000 --- a/routes/ecash.js +++ /dev/null @@ -1,127 +0,0 @@ -import { g, s, db } from "$lib/db"; -import { claim, mint, check } from "$lib/ecash"; -import { bail } from "$lib/utils"; -import { debit, credit, types } from "$lib/payments"; -import { v4 } from "uuid"; -import { l, err, warn } from "$lib/logging"; -import { emit } from "$lib/sockets"; -import store from "$lib/store"; - -let { ecash: type } = types; - -export default { - async save({ body: { token } }, res) { - try { - let id = v4(); - await s(`cash:${id}`, token); - res.send({ id }); - } catch (e) { - err(e.message); - bail(res, e.message); - } - }, - - async get({ params: { id } }, res) { - try { - let token = await g(`cash:${id}`); - let status = await check(token); - res.send({ token, status }); - } catch (e) { - err(e.message); - bail(res, e.message); - } - }, - - async claim({ body: { token }, user }, res) { - try { - let amount = await claim(token); - - let memo; - let hash = v4(); - let { currency, id: uid } = user; - let rates = await g("rates"); - await s(`invoice:${hash}`, { - currency, - id: hash, - hash, - rate: rates[currency], - uid, - received: 0, - }); - - await credit(hash, amount, memo, user.id, type); - - res.send({ ok: true }); - } catch (e) { - err(e.message); - bail(res, e.message); - } - }, - - async mint({ body: { amount }, user }, res) { - try { - let id = v4(); - let hash = v4(); - - let p = await debit({ hash, amount, user, type }); - let token = await mint(amount); - p.memo = token; - await s(`payment:${p.id}`, p); - s(`cash:${id}`, token); - - res.send({ id }); - } catch (e) { - err(e.message); - bail(res, e.message); - } - }, - - async melt({ body: { amount, bolt11: hash, preimage }, user }, res) { - try { - amount = Math.round(amount / 1000); - let ref = preimage; - let { lightning: type } = types; - if (user.username !== "mint") fail("unauthorized"); - let { id: uid, currency, username } = user; - let ourfee = await db.debit( - `balance:${uid}`, - `credit:${type}:${uid}`, - amount || 0, - 0, - 0, - 0, - ); - - let rates = await g("rates"); - let rate = rates[currency]; - - if (ourfee.err) fail(ourfee.err); - - let id = v4(); - let p = { - id, - amount: -amount, - hash, - ourfee, - uid, - confirmed: true, - rate, - currency, - type, - ref, - created: Date.now(), - }; - - await s(`payment:${hash}`, id); - await s(`payment:${id}`, p); - await db.lPush(`${uid}:payments`, id); - - l(user.username, "sent", type, amount); - emit(user.id, "payment", p); - - res.send(p); - } catch (e) { - bail(res, e.message); - } - }, -}; diff --git a/routes/ecash.js.map b/routes/ecash.js.map new file mode 100644 index 00000000..42dfc998 --- /dev/null +++ b/routes/ecash.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ecash.js","sourceRoot":"","sources":["ecash.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/ecash.ts b/routes/ecash.ts new file mode 100644 index 00000000..8c2684b1 --- /dev/null +++ b/routes/ecash.ts @@ -0,0 +1,127 @@ +import { g, s, db } from "$lib/db"; +import { claim, mint, check } from "$lib/ecash"; +import { bail } from "$lib/utils"; +import { debit, credit, types } from "$lib/payments"; +import { v4 } from "uuid"; +import { l, err, warn } from "$lib/logging"; +import { emit } from "$lib/sockets"; +import store from "$lib/store"; + +let { ecash: type } = types; + +export default { + async save({ body: { token } }, res) { + try { + let id = v4(); + await s(`cash:${id}`, token); + res.send({ id }); + } catch (e) { + err(e.message); + bail(res, e.message); + } + }, + + async get({ params: { id } }, res) { + try { + let token = await g(`cash:${id}`); + let status = await check(token); + res.send({ token, status }); + } catch (e) { + err(e.message); + bail(res, e.message); + } + }, + + async claim({ body: { token }, user }, res) { + try { + let amount = await claim(token); + + let memo; + let hash = v4(); + let { currency, id: uid } = user; + let rates = await g("rates"); + await s(`invoice:${hash}`, { + currency, + id: hash, + hash, + rate: rates[currency], + uid, + received: 0, + }); + + await credit(hash, amount, memo, user.id, type); + + res.send({ ok: true }); + } catch (e) { + err(e.message); + bail(res, e.message); + } + }, + + async mint({ body: { amount }, user }, res) { + try { + let id = v4(); + let hash = v4(); + + let p = await debit({ hash, amount, user, type }); + let token = await mint(amount); + p.memo = token; + await s(`payment:${p.id}`, p); + s(`cash:${id}`, token); + + res.send({ id }); + } catch (e) { + err(e.message); + bail(res, e.message); + } + }, + + async melt({ body: { amount, bolt11: hash, preimage }, user }, res) { + try { + amount = Math.round(amount / 1000); + let ref = preimage; + let { lightning: type } = types; + if (user.username !== "mint") fail("unauthorized"); + let { id: uid, currency, username } = user; + let ourfee = await db.debit( + `balance:${uid}`, + `credit:${type}:${uid}`, + amount || 0, + 0, + 0, + 0, + ); + + let rates = await g("rates"); + let rate = rates[currency]; + + if (ourfee.err) fail(ourfee.err); + + let id = v4(); + let p = { + id, + amount: -amount, + hash, + ourfee, + uid, + confirmed: true, + rate, + currency, + type, + ref, + created: Date.now(), + }; + + await s(`payment:${hash}`, id); + await s(`payment:${id}`, p); + await db.lPush(`${uid}:payments`, id); + + l(user.username, "sent", type, amount); + emit(user.id, "payment", p); + + res.send(p); + } catch (e) { + bail(res, e.message); + } + }, +}; diff --git a/routes/email.js b/routes/email.js deleted file mode 100644 index 63c88ab0..00000000 --- a/routes/email.js +++ /dev/null @@ -1,61 +0,0 @@ -import { bail } from "$lib/utils"; -import got from "got"; -import config from "$config"; -import { l, warn } from "$lib/logging"; -import { SESClient } from "@aws-sdk/client-ses"; -import { SendEmailCommand } from "@aws-sdk/client-ses"; - -export default { - async send({ body }, res) { - try { - let { email, message, username, token: response } = body; - - let Charset = "UTF-8"; - - let { recaptcha: secret } = config; - let { success } = await got - .post("https://www.google.com/recaptcha/api/siteverify", { - form: { - secret, - response, - }, - }) - .json(); - - if (success || response === config.adminpass) { - delete body.token; - - warn("support request from", email); - let client = new SESClient({ region: "us-east-2" }); - await client.send( - new SendEmailCommand({ - Destination: { - CcAddresses: [], - ToAddresses: [config.support], - }, - Message: { - Body: { - Html: { Charset, Data: message.replace(/\n/g, "
") }, - Text: { Charset, Data: message }, - }, - Subject: { - Charset, - Data: - body.subject || - "Support Request" + (username ? " From " + username : ""), - }, - }, - ReplyToAddresses: [email], - Source: config.support, - }), - ); - - res.send({ ok: true }); - } else { - bail(res, "failed captcha"); - } - } catch (e) { - console.log(e); - } - }, -}; diff --git a/routes/email.js.map b/routes/email.js.map new file mode 100644 index 00000000..fb6f1acd --- /dev/null +++ b/routes/email.js.map @@ -0,0 +1 @@ +{"version":3,"file":"email.js","sourceRoot":"","sources":["email.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oCAAkC;AAClC,4CAAsB;AACtB,oDAA6B;AAC7B,wCAAuC;AACvC,kDAAgD;AAChD,kDAAuD;AAEvD,kBAAe;IACP,IAAI;4DAAC,EAAQ,EAAE,GAAG;;gBAAX,IAAI,UAAA;;;;;wBAEP,KAAK,GAAyC,IAAI,MAA7C,EAAE,OAAO,GAAgC,IAAI,QAApC,EAAE,QAAQ,GAAsB,IAAI,SAA1B,EAAS,QAAQ,GAAK,IAAI,MAAT,CAAU;wBAErD,OAAO,GAAG,OAAO,CAAC;wBAEL,MAAM,GAAK,iBAAM,UAAX,CAAY;wBACjB,qBAAM,aAAG;iCACxB,IAAI,CAAC,iDAAiD,EAAE;gCACvD,IAAI,EAAE;oCACJ,MAAM,QAAA;oCACN,QAAQ,UAAA;iCACT;6BACF,CAAC;iCACD,IAAI,EAAE,EAAA;;wBAPH,OAAO,GAAK,CAAA,SAOT,CAAA,QAPI;6BAST,CAAA,OAAO,IAAI,QAAQ,KAAK,iBAAM,CAAC,SAAS,CAAA,EAAxC,wBAAwC;wBAC1C,OAAO,IAAI,CAAC,KAAK,CAAC;wBAElB,IAAA,cAAI,EAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;wBAChC,MAAM,GAAG,IAAI,sBAAS,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;wBACpD,qBAAM,MAAM,CAAC,IAAI,CACf,IAAI,6BAAgB,CAAC;gCACnB,WAAW,EAAE;oCACX,WAAW,EAAE,EAAE;oCACf,WAAW,EAAE,CAAC,iBAAM,CAAC,OAAO,CAAC;iCAC9B;gCACD,OAAO,EAAE;oCACP,IAAI,EAAE;wCACJ,IAAI,EAAE,EAAE,OAAO,SAAA,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE;wCACvD,IAAI,EAAE,EAAE,OAAO,SAAA,EAAE,IAAI,EAAE,OAAO,EAAE;qCACjC;oCACD,OAAO,EAAE;wCACP,OAAO,SAAA;wCACP,IAAI,EACF,IAAI,CAAC,OAAO;4CACZ,iBAAiB,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;qCAC5D;iCACF;gCACD,gBAAgB,EAAE,CAAC,KAAK,CAAC;gCACzB,MAAM,EAAE,iBAAM,CAAC,OAAO;6BACvB,CAAC,CACH,EAAA;;wBArBD,SAqBC,CAAC;wBAEF,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;;wBAEvB,IAAA,YAAI,EAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;;;;;wBAG9B,OAAO,CAAC,GAAG,CAAC,GAAC,CAAC,CAAC;;;;;;KAElB;CACF,CAAC"} \ No newline at end of file diff --git a/routes/email.ts b/routes/email.ts new file mode 100644 index 00000000..63c88ab0 --- /dev/null +++ b/routes/email.ts @@ -0,0 +1,61 @@ +import { bail } from "$lib/utils"; +import got from "got"; +import config from "$config"; +import { l, warn } from "$lib/logging"; +import { SESClient } from "@aws-sdk/client-ses"; +import { SendEmailCommand } from "@aws-sdk/client-ses"; + +export default { + async send({ body }, res) { + try { + let { email, message, username, token: response } = body; + + let Charset = "UTF-8"; + + let { recaptcha: secret } = config; + let { success } = await got + .post("https://www.google.com/recaptcha/api/siteverify", { + form: { + secret, + response, + }, + }) + .json(); + + if (success || response === config.adminpass) { + delete body.token; + + warn("support request from", email); + let client = new SESClient({ region: "us-east-2" }); + await client.send( + new SendEmailCommand({ + Destination: { + CcAddresses: [], + ToAddresses: [config.support], + }, + Message: { + Body: { + Html: { Charset, Data: message.replace(/\n/g, "
") }, + Text: { Charset, Data: message }, + }, + Subject: { + Charset, + Data: + body.subject || + "Support Request" + (username ? " From " + username : ""), + }, + }, + ReplyToAddresses: [email], + Source: config.support, + }), + ); + + res.send({ ok: true }); + } else { + bail(res, "failed captcha"); + } + } catch (e) { + console.log(e); + } + }, +}; diff --git a/routes/info.js b/routes/info.js deleted file mode 100644 index f2e262f6..00000000 --- a/routes/info.js +++ /dev/null @@ -1,32 +0,0 @@ -import { archive } from "$lib/db"; -import db from "$lib/db"; -import ln from "$lib/ln"; - -export default { - async balances(req, res) { - let lnchannel; - let lnwallet; - - let total = 0; - - for await (let k of db.scanIterator({ MATCH: "balance:*" })) { - total += parseInt(await db.get(k)); - } - - for await (let k of archive.scanIterator({ MATCH: "balance:*" })) { - total += parseInt(await archive.get(k)); - } - - const funds = await ln.listfunds(); - lnchannel = parseInt(funds.channels.reduce((a, b) => a + b.channel_sat, 0)); - lnwallet = parseInt(funds.outputs.reduce((a, b) => a + b.value, 0)); - - const info = { - lnchannel, - lnwallet, - total, - }; - - res.send(info); - }, -}; diff --git a/routes/info.js.map b/routes/info.js.map new file mode 100644 index 00000000..d230bd05 --- /dev/null +++ b/routes/info.js.map @@ -0,0 +1 @@ +{"version":3,"file":"info.js","sourceRoot":"","sources":["info.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8BAAkC;AAClC,+CAAyB;AACzB,+CAAyB;AAEzB,kBAAe;IACP,QAAQ,YAAC,GAAG,EAAE,GAAG;;;;;;;wBAIjB,KAAK,GAAG,CAAC,CAAC;;;;mCAEM,KAAA,cAAA,YAAE,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;;;;;wBAAvC,cAAuC;wBAAvC,WAAuC;wBAA5C,CAAC,KAAA,CAAA;wBACd,KAAA,KAAK,CAAA;wBAAI,KAAA,QAAQ,CAAA;wBAAC,qBAAM,YAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAA;;wBAAjC,KAAK,GAAL,KAAS,kBAAS,SAAe,EAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;mCAGjB,KAAA,cAAA,YAAO,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;;;;;wBAA5C,cAA4C;wBAA5C,WAA4C;wBAAjD,CAAC,KAAA,CAAA;wBACd,KAAA,KAAK,CAAA;wBAAI,KAAA,QAAQ,CAAA;wBAAC,qBAAM,YAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAA;;wBAAtC,KAAK,GAAL,KAAS,kBAAS,SAAoB,EAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;6BAG5B,qBAAM,YAAE,CAAC,SAAS,EAAE,EAAA;;wBAA5B,KAAK,GAAG,SAAoB;wBAClC,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAC,CAAC,EAAE,CAAC,IAAK,OAAA,CAAC,GAAG,CAAC,CAAC,WAAW,EAAjB,CAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;wBAC5E,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,UAAC,CAAC,EAAE,CAAC,IAAK,OAAA,CAAC,GAAG,CAAC,CAAC,KAAK,EAAX,CAAW,EAAE,CAAC,CAAC,CAAC,CAAC;wBAE9D,IAAI,GAAG;4BACX,SAAS,WAAA;4BACT,QAAQ,UAAA;4BACR,KAAK,OAAA;yBACN,CAAC;wBAEF,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;;;;KAChB;CACF,CAAC"} \ No newline at end of file diff --git a/routes/info.ts b/routes/info.ts new file mode 100644 index 00000000..f2e262f6 --- /dev/null +++ b/routes/info.ts @@ -0,0 +1,32 @@ +import { archive } from "$lib/db"; +import db from "$lib/db"; +import ln from "$lib/ln"; + +export default { + async balances(req, res) { + let lnchannel; + let lnwallet; + + let total = 0; + + for await (let k of db.scanIterator({ MATCH: "balance:*" })) { + total += parseInt(await db.get(k)); + } + + for await (let k of archive.scanIterator({ MATCH: "balance:*" })) { + total += parseInt(await archive.get(k)); + } + + const funds = await ln.listfunds(); + lnchannel = parseInt(funds.channels.reduce((a, b) => a + b.channel_sat, 0)); + lnwallet = parseInt(funds.outputs.reduce((a, b) => a + b.value, 0)); + + const info = { + lnchannel, + lnwallet, + total, + }; + + res.send(info); + }, +}; diff --git a/routes/invoices.js b/routes/invoices.js deleted file mode 100644 index 5cc60e05..00000000 --- a/routes/invoices.js +++ /dev/null @@ -1,38 +0,0 @@ -import { g, s } from "$lib/db"; -import { generate } from "$lib/invoices"; -import { bail, fail, pick } from "$lib/utils"; -import whitelist from "$lib/whitelist"; -import got from "got"; -import config from "$config"; -import ln from "$lib/ln"; - -export default { - async get({ params: { id } }, res) { - let invoice = await g(`invoice:${id}`); - if (typeof invoice === "string") invoice = await g(`invoice:${invoice}`); - - if (invoice) { - delete invoice.secret; - invoice.user = pick(await g(`user:${invoice.uid}`), [ - "id", - "profile", - "banner", - "currency", - "username", - "pubkey", - ]); - - invoice.items ||= []; - } - if (invoice) res.send(invoice); - else res.code(500).send("invoice not found"); - }, - - async create({ body: { invoice, user }, user: sender }, res) { - try { - res.send(await generate({ invoice, user, sender })); - } catch (e) { - bail(res, e.message); - } - }, -}; diff --git a/routes/invoices.js.map b/routes/invoices.js.map new file mode 100644 index 00000000..183f297d --- /dev/null +++ b/routes/invoices.js.map @@ -0,0 +1 @@ +{"version":3,"file":"invoices.js","sourceRoot":"","sources":["invoices.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8BAA+B;AAC/B,0CAAyC;AACzC,oCAA8C;AAM9C,kBAAe;IACP,GAAG;4DAAC,EAAkB,EAAE,GAAG;;gBAAX,EAAE,eAAA;;;4BACR,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;;wBAAlC,OAAO,GAAG,SAAwB;6BAClC,CAAA,OAAO,OAAO,KAAK,QAAQ,CAAA,EAA3B,wBAA2B;wBAAY,qBAAM,IAAA,MAAC,EAAC,kBAAW,OAAO,CAAE,CAAC,EAAA;;wBAAvC,OAAO,GAAG,SAA6B,CAAC;;;6BAErE,OAAO,EAAP,wBAAO;wBACT,OAAO,OAAO,CAAC,MAAM,CAAC;wBACtB,KAAA,OAAO,CAAA;wBAAQ,KAAA,YAAI,CAAA;wBAAC,qBAAM,IAAA,MAAC,EAAC,eAAQ,OAAO,CAAC,GAAG,CAAE,CAAC,EAAA;;wBAAlD,GAAQ,IAAI,GAAG,kBAAK,SAA8B,EAAE;gCAClD,IAAI;gCACJ,SAAS;gCACT,QAAQ;gCACR,UAAU;gCACV,UAAU;gCACV,QAAQ;6BACT,EAAC,CAAC;wBAEH,OAAO,CAAC,KAAK,KAAb,OAAO,CAAC,KAAK,GAAK,EAAE,EAAC;;;wBAEvB,IAAI,OAAO;4BAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;4BAC1B,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;;;;;KAC9C;IAEK,MAAM;4DAAC,EAAyC,EAAE,GAAG;;gBAA5C,YAAuB,EAAf,OAAO,aAAA,EAAE,IAAI,UAAA,EAAU,MAAM,UAAA;;;;;wBAEhD,KAAA,CAAA,KAAA,GAAG,CAAA,CAAC,IAAI,CAAA;wBAAC,qBAAM,IAAA,mBAAQ,EAAC,EAAE,OAAO,SAAA,EAAE,IAAI,MAAA,EAAE,MAAM,QAAA,EAAE,CAAC,EAAA;;wBAAlD,cAAS,SAAyC,EAAC,CAAC;;;;wBAEpD,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;CACF,CAAC"} \ No newline at end of file diff --git a/routes/invoices.ts b/routes/invoices.ts new file mode 100644 index 00000000..5cc60e05 --- /dev/null +++ b/routes/invoices.ts @@ -0,0 +1,38 @@ +import { g, s } from "$lib/db"; +import { generate } from "$lib/invoices"; +import { bail, fail, pick } from "$lib/utils"; +import whitelist from "$lib/whitelist"; +import got from "got"; +import config from "$config"; +import ln from "$lib/ln"; + +export default { + async get({ params: { id } }, res) { + let invoice = await g(`invoice:${id}`); + if (typeof invoice === "string") invoice = await g(`invoice:${invoice}`); + + if (invoice) { + delete invoice.secret; + invoice.user = pick(await g(`user:${invoice.uid}`), [ + "id", + "profile", + "banner", + "currency", + "username", + "pubkey", + ]); + + invoice.items ||= []; + } + if (invoice) res.send(invoice); + else res.code(500).send("invoice not found"); + }, + + async create({ body: { invoice, user }, user: sender }, res) { + try { + res.send(await generate({ invoice, user, sender })); + } catch (e) { + bail(res, e.message); + } + }, +}; diff --git a/routes/items.js b/routes/items.js deleted file mode 100644 index 9b6314fa..00000000 --- a/routes/items.js +++ /dev/null @@ -1,70 +0,0 @@ -import { g, s, db } from "$lib/db"; -import { v4 } from "uuid"; -import { bail, fail } from "$lib/utils"; - -export default { - async list({ params: { id: uid } }, res) { - let items = []; - for (let id of await db.lRange(`${uid}:items`, 0, -1)) { - let item = await g(`item:${id}`); - if (item) items.push(item); - else await db.lRem(`${uid}:items`, 0, id); - } - - res.send(items); - }, - - async get({ params: { id } }, res) { - try { - let item = await g(`item:${id}`); - if (!item) fail("Item not found"); - res.send(item); - } catch (e) { - bail(res, e.message); - } - }, - - async create({ body: item, user: { id } }, res) { - try { - if (!item.id) { - item.id = v4(); - await db.lPush(`${id}:items`, item.id); - } - - if (!parseFloat(item.price)) fail("Invalid price"); - item.name = item.name.replace(/[^a-zA-Z0-9 ]/g, ""); - - await s(`item:${item.id}`, item); - - res.send(item); - } catch (e) { - bail(res, e.message); - } - }, - - async del({ body: { item }, user: { id } }, res) { - try { - let n = await db.lRem(`${id}:items`, 0, item.id); - if (n) db.del(`item:${item.id}`); - else fail("item not found"); - - res.send({}); - } catch (e) { - bail(res, e.message); - } - }, - - async sort({ body: { items }, user: { id } }, res) { - try { - await db.del(`${id}:items`); - - for (let item of items) { - await db.rPush(`${id}:items`, item.id); - } - - res.send({}); - } catch (e) { - bail(res, e.message); - } - }, -}; diff --git a/routes/items.js.map b/routes/items.js.map new file mode 100644 index 00000000..adebdcbc --- /dev/null +++ b/routes/items.js.map @@ -0,0 +1 @@ +{"version":3,"file":"items.js","sourceRoot":"","sources":["items.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/items.ts b/routes/items.ts new file mode 100644 index 00000000..9b6314fa --- /dev/null +++ b/routes/items.ts @@ -0,0 +1,70 @@ +import { g, s, db } from "$lib/db"; +import { v4 } from "uuid"; +import { bail, fail } from "$lib/utils"; + +export default { + async list({ params: { id: uid } }, res) { + let items = []; + for (let id of await db.lRange(`${uid}:items`, 0, -1)) { + let item = await g(`item:${id}`); + if (item) items.push(item); + else await db.lRem(`${uid}:items`, 0, id); + } + + res.send(items); + }, + + async get({ params: { id } }, res) { + try { + let item = await g(`item:${id}`); + if (!item) fail("Item not found"); + res.send(item); + } catch (e) { + bail(res, e.message); + } + }, + + async create({ body: item, user: { id } }, res) { + try { + if (!item.id) { + item.id = v4(); + await db.lPush(`${id}:items`, item.id); + } + + if (!parseFloat(item.price)) fail("Invalid price"); + item.name = item.name.replace(/[^a-zA-Z0-9 ]/g, ""); + + await s(`item:${item.id}`, item); + + res.send(item); + } catch (e) { + bail(res, e.message); + } + }, + + async del({ body: { item }, user: { id } }, res) { + try { + let n = await db.lRem(`${id}:items`, 0, item.id); + if (n) db.del(`item:${item.id}`); + else fail("item not found"); + + res.send({}); + } catch (e) { + bail(res, e.message); + } + }, + + async sort({ body: { items }, user: { id } }, res) { + try { + await db.del(`${id}:items`); + + for (let item of items) { + await db.rPush(`${id}:items`, item.id); + } + + res.send({}); + } catch (e) { + bail(res, e.message); + } + }, +}; diff --git a/routes/lnurl.js b/routes/lnurl.js deleted file mode 100644 index 462127ac..00000000 --- a/routes/lnurl.js +++ /dev/null @@ -1,135 +0,0 @@ -import migrate from "$lib/migrate"; -import { g, s } from "$lib/db"; -import { l, err, warn } from "$lib/logging"; -import { bail, getUser, fail } from "$lib/utils"; -import { v4 } from "uuid"; -import got from "got"; -import { generate } from "$lib/invoices"; -import { bech32 } from "bech32"; -import { fields, pick } from "$lib/utils"; -import { types } from "$lib/payments"; -import crypto from "crypto"; -import { COINOS_PUBKEY } from "$lib/nostr"; - -import config from "$config"; -let { admin } = config; - -let { URL } = process.env; -let host = URL.split("/").at(-1); - -export default { - async encode({ query: { address } }, res) { - let [name, domain] = address.split("@"); - let url = `https://${domain}/.well-known/lnurlp/${name}`; - - try { - let r = await got(url).json(); - if (r.tag !== "payRequest") fail("not an ln address"); - } catch (e) { - let m = `failed to lookup lightning address ${address}`; - warn(m); - return bail(res, m); - } - - let enc = bech32.encode("lnurl", bech32.toWords(Buffer.from(url)), 20000); - res.send(enc); - }, - - async decode({ query: { text } }, res) { - try { - let url = Buffer.from( - bech32.fromWords(bech32.decode(text, 20000).words), - ).toString(); - - res.send(await got(url).json()); - } catch (e) { - bail(res, e.message); - } - }, - - async lnurlp( - { - params: { username }, - query: { minSendable = 1000, maxSendable = 100000000000 }, - }, - res, - ) { - try { - let { id: uid } = await getUser(username); - - let metadata = JSON.stringify([ - ["text/plain", `Paying ${username}@${host}`], - ["text/identifier", `${username}@${host}`], - ]); - - let id = v4(); - await s(`lnurl:${id}`, uid); - - res.send({ - allowsNostr: true, - minSendable, - maxSendable, - metadata, - nostrPubkey: COINOS_PUBKEY, - callback: `${URL}/api/lnurl/${id}`, - tag: "payRequest", - }); - } catch (e) { - warn("problem generating lnurlp request", username, e.message); - bail(res, e.message); - } - }, - - async lnurlw({ params: { k1, pr } }, res) {}, - - async lnurl({ params: { id }, query: { amount, nostr } }, res) { - try { - let uid = await g(`lnurl:${id}`); - let user = await getUser(uid); - if (!user) fail("user not found"); - let { username } = user; - username = username.replace(/\s/g, "").toLowerCase(); - - let memo = `Paying ${username}@${host}`; - let metadata = JSON.stringify([ - ["text/plain", memo], - ["text/identifier", `${username}@${host}`], - ]); - - if (nostr) { - try { - let event = JSON.parse(decodeURIComponent(nostr)); - // TODO: validate the event - await s(`zap:${id}`, event); - metadata = nostr; - } catch (e) { - err("problem handling zap", e.message); - } - } - - let { id: iid, text: pr } = await generate({ - invoice: { - amount: Math.round(amount / 1000), - memo: metadata, - type: types.lightning, - }, - user, - }); - - res.send({ pr, routes: [], verify: `${URL}/api/lnurl/verify/${iid}` }); - } catch (e) { - bail(res, e.message); - } - }, - - async verify({ params: { id } }, res) { - let inv = await g(`invoice:${id}`); - - if (!inv) return res.send({ status: "ERROR", reason: "Not found" }); - - let { received, amount, preimage } = inv; - let settled = received >= amount; - - res.send({ status: "OK", settled, preimage }); - }, -}; diff --git a/routes/lnurl.js.map b/routes/lnurl.js.map new file mode 100644 index 00000000..58ef53e3 --- /dev/null +++ b/routes/lnurl.js.map @@ -0,0 +1 @@ +{"version":3,"file":"lnurl.js","sourceRoot":"","sources":["lnurl.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,8BAA+B;AAC/B,wCAA4C;AAC5C,oCAAiD;AACjD,6BAA0B;AAC1B,4CAAsB;AACtB,0CAAyC;AACzC,iCAAgC;AAEhC,0CAAsC;AAEtC,oCAA2C;AAE3C,oDAA6B;AACvB,IAAA,KAAK,GAAK,iBAAM,MAAX,CAAY;AAEjB,IAAA,GAAG,GAAK,OAAO,CAAC,GAAG,IAAhB,CAAiB;AAC1B,IAAI,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAEjC,kBAAe;IACP,MAAM;4DAAC,EAAsB,EAAE,GAAG;;gBAAhB,OAAO,mBAAA;;;;wBACzB,KAAiB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAlC,IAAI,QAAA,EAAE,MAAM,QAAA,CAAuB;wBACpC,GAAG,GAAG,kBAAW,MAAM,iCAAuB,IAAI,CAAE,CAAC;;;;wBAG/C,qBAAM,IAAA,aAAG,EAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAA;;wBAAzB,CAAC,GAAG,SAAqB;wBAC7B,IAAI,CAAC,CAAC,GAAG,KAAK,YAAY;4BAAE,IAAA,YAAI,EAAC,mBAAmB,CAAC,CAAC;;;;wBAElD,CAAC,GAAG,6CAAsC,OAAO,CAAE,CAAC;wBACxD,IAAA,cAAI,EAAC,CAAC,CAAC,CAAC;wBACR,sBAAO,IAAA,YAAI,EAAC,GAAG,EAAE,CAAC,CAAC,EAAC;;wBAGlB,GAAG,GAAG,eAAM,CAAC,MAAM,CAAC,OAAO,EAAE,eAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;wBAC1E,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;;;;KACf;IAEK,MAAM;4DAAC,EAAmB,EAAE,GAAG;;gBAAb,IAAI,gBAAA;;;;;wBAEpB,GAAG,GAAG,MAAM,CAAC,IAAI,CACnB,eAAM,CAAC,SAAS,CAAC,eAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,CACnD,CAAC,QAAQ,EAAE,CAAC;wBAEb,KAAA,CAAA,KAAA,GAAG,CAAA,CAAC,IAAI,CAAA;wBAAC,qBAAM,IAAA,aAAG,EAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAA;;wBAA9B,cAAS,SAAqB,EAAC,CAAC;;;;wBAEhC,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,MAAM;4DACV,EAGC,EACD,GAAG;;gBAHS,QAAQ,qBAAA,EAClB,aAAyD,EAAhD,mBAAkB,EAAlB,WAAW,mBAAG,IAAI,KAAA,EAAE,mBAA0B,EAA1B,WAAW,mBAAG,YAAY,KAAA;;;;;wBAKrC,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;wBAA/B,GAAG,GAAK,CAAA,SAAuB,CAAA,GAA5B;wBAET,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;4BAC5B,CAAC,YAAY,EAAE,iBAAU,QAAQ,cAAI,IAAI,CAAE,CAAC;4BAC5C,CAAC,iBAAiB,EAAE,UAAG,QAAQ,cAAI,IAAI,CAAE,CAAC;yBAC3C,CAAC,CAAC;wBAEC,EAAE,GAAG,IAAA,SAAE,GAAE,CAAC;wBACd,qBAAM,IAAA,MAAC,EAAC,gBAAS,EAAE,CAAE,EAAE,GAAG,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAE5B,GAAG,CAAC,IAAI,CAAC;4BACP,WAAW,EAAE,IAAI;4BACjB,WAAW,aAAA;4BACX,WAAW,aAAA;4BACX,QAAQ,UAAA;4BACR,WAAW,EAAE,qBAAa;4BAC1B,QAAQ,EAAE,UAAG,GAAG,wBAAc,EAAE,CAAE;4BAClC,GAAG,EAAE,YAAY;yBAClB,CAAC,CAAC;;;;wBAEH,IAAA,cAAI,EAAC,mCAAmC,EAAE,QAAQ,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;wBAC/D,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,MAAM;4DAAC,EAAsB,EAAE,GAAG;gBAAzB,cAAkB,EAAR,EAAE,QAAA,EAAE,EAAE,QAAA;;;;;KAAa;IAEtC,KAAK;4DAAC,EAA4C,EAAE,GAAG;;gBAArC,EAAE,eAAA,EAAI,aAAwB,EAAf,MAAM,YAAA,EAAE,KAAK,WAAA;;;;;wBAEtC,qBAAM,IAAA,MAAC,EAAC,gBAAS,EAAE,CAAE,CAAC,EAAA;;wBAA5B,GAAG,GAAG,SAAsB;wBACrB,qBAAM,IAAA,eAAO,EAAC,GAAG,CAAC,EAAA;;wBAAzB,IAAI,GAAG,SAAkB;wBAC7B,IAAI,CAAC,IAAI;4BAAE,IAAA,YAAI,EAAC,gBAAgB,CAAC,CAAC;wBAC5B,QAAQ,GAAK,IAAI,SAAT,CAAU;wBACxB,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;wBAEjD,IAAI,GAAG,iBAAU,QAAQ,cAAI,IAAI,CAAE,CAAC;wBACpC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;4BAC5B,CAAC,YAAY,EAAE,IAAI,CAAC;4BACpB,CAAC,iBAAiB,EAAE,UAAG,QAAQ,cAAI,IAAI,CAAE,CAAC;yBAC3C,CAAC,CAAC;6BAEC,KAAK,EAAL,wBAAK;;;;wBAED,UAAQ,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;wBAClD,2BAA2B;wBAC3B,qBAAM,IAAA,MAAC,EAAC,cAAO,EAAE,CAAE,EAAE,OAAK,CAAC,EAAA;;wBAD3B,2BAA2B;wBAC3B,SAA2B,CAAC;wBAC5B,QAAQ,GAAG,KAAK,CAAC;;;;wBAEjB,IAAA,aAAG,EAAC,sBAAsB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;4BAIf,qBAAM,IAAA,mBAAQ,EAAC;4BACzC,OAAO,EAAE;gCACP,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;gCACjC,IAAI,EAAE,QAAQ;gCACd,IAAI,EAAE,gBAAK,CAAC,SAAS;6BACtB;4BACD,IAAI,MAAA;yBACL,CAAC,EAAA;;wBAPE,KAAwB,SAO1B,EAPQ,GAAG,QAAA,EAAQ,EAAE,UAAA;wBASvB,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAA,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,UAAG,GAAG,+BAAqB,GAAG,CAAE,EAAE,CAAC,CAAC;;;;wBAEvE,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,MAAM;4DAAC,EAAkB,EAAE,GAAG;;gBAAX,EAAE,eAAA;;;4BACf,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;;wBAA9B,GAAG,GAAG,SAAwB;wBAElC,IAAI,CAAC,GAAG;4BAAE,sBAAO,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,EAAC;wBAE9D,QAAQ,GAAuB,GAAG,SAA1B,EAAE,MAAM,GAAe,GAAG,OAAlB,EAAE,QAAQ,GAAK,GAAG,SAAR,CAAS;wBACrC,OAAO,GAAG,QAAQ,IAAI,MAAM,CAAC;wBAEjC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,SAAA,EAAE,QAAQ,UAAA,EAAE,CAAC,CAAC;;;;;KAC/C;CACF,CAAC"} \ No newline at end of file diff --git a/routes/lnurl.ts b/routes/lnurl.ts new file mode 100644 index 00000000..462127ac --- /dev/null +++ b/routes/lnurl.ts @@ -0,0 +1,135 @@ +import migrate from "$lib/migrate"; +import { g, s } from "$lib/db"; +import { l, err, warn } from "$lib/logging"; +import { bail, getUser, fail } from "$lib/utils"; +import { v4 } from "uuid"; +import got from "got"; +import { generate } from "$lib/invoices"; +import { bech32 } from "bech32"; +import { fields, pick } from "$lib/utils"; +import { types } from "$lib/payments"; +import crypto from "crypto"; +import { COINOS_PUBKEY } from "$lib/nostr"; + +import config from "$config"; +let { admin } = config; + +let { URL } = process.env; +let host = URL.split("/").at(-1); + +export default { + async encode({ query: { address } }, res) { + let [name, domain] = address.split("@"); + let url = `https://${domain}/.well-known/lnurlp/${name}`; + + try { + let r = await got(url).json(); + if (r.tag !== "payRequest") fail("not an ln address"); + } catch (e) { + let m = `failed to lookup lightning address ${address}`; + warn(m); + return bail(res, m); + } + + let enc = bech32.encode("lnurl", bech32.toWords(Buffer.from(url)), 20000); + res.send(enc); + }, + + async decode({ query: { text } }, res) { + try { + let url = Buffer.from( + bech32.fromWords(bech32.decode(text, 20000).words), + ).toString(); + + res.send(await got(url).json()); + } catch (e) { + bail(res, e.message); + } + }, + + async lnurlp( + { + params: { username }, + query: { minSendable = 1000, maxSendable = 100000000000 }, + }, + res, + ) { + try { + let { id: uid } = await getUser(username); + + let metadata = JSON.stringify([ + ["text/plain", `Paying ${username}@${host}`], + ["text/identifier", `${username}@${host}`], + ]); + + let id = v4(); + await s(`lnurl:${id}`, uid); + + res.send({ + allowsNostr: true, + minSendable, + maxSendable, + metadata, + nostrPubkey: COINOS_PUBKEY, + callback: `${URL}/api/lnurl/${id}`, + tag: "payRequest", + }); + } catch (e) { + warn("problem generating lnurlp request", username, e.message); + bail(res, e.message); + } + }, + + async lnurlw({ params: { k1, pr } }, res) {}, + + async lnurl({ params: { id }, query: { amount, nostr } }, res) { + try { + let uid = await g(`lnurl:${id}`); + let user = await getUser(uid); + if (!user) fail("user not found"); + let { username } = user; + username = username.replace(/\s/g, "").toLowerCase(); + + let memo = `Paying ${username}@${host}`; + let metadata = JSON.stringify([ + ["text/plain", memo], + ["text/identifier", `${username}@${host}`], + ]); + + if (nostr) { + try { + let event = JSON.parse(decodeURIComponent(nostr)); + // TODO: validate the event + await s(`zap:${id}`, event); + metadata = nostr; + } catch (e) { + err("problem handling zap", e.message); + } + } + + let { id: iid, text: pr } = await generate({ + invoice: { + amount: Math.round(amount / 1000), + memo: metadata, + type: types.lightning, + }, + user, + }); + + res.send({ pr, routes: [], verify: `${URL}/api/lnurl/verify/${iid}` }); + } catch (e) { + bail(res, e.message); + } + }, + + async verify({ params: { id } }, res) { + let inv = await g(`invoice:${id}`); + + if (!inv) return res.send({ status: "ERROR", reason: "Not found" }); + + let { received, amount, preimage } = inv; + let settled = received >= amount; + + res.send({ status: "OK", settled, preimage }); + }, +}; diff --git a/routes/locations.js b/routes/locations.js deleted file mode 100644 index e9a6843c..00000000 --- a/routes/locations.js +++ /dev/null @@ -1,8 +0,0 @@ -import { g } from "$lib/db"; - -export default { - async list(req, res) { - let locations = await g("locations"); - res.send({ locations }); - }, -}; diff --git a/routes/locations.js.map b/routes/locations.js.map new file mode 100644 index 00000000..d4eda525 --- /dev/null +++ b/routes/locations.js.map @@ -0,0 +1 @@ +{"version":3,"file":"locations.js","sourceRoot":"","sources":["locations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8BAA4B;AAE5B,kBAAe;IACP,IAAI,YAAC,GAAG,EAAE,GAAG;;;;;4BACD,qBAAM,IAAA,MAAC,EAAC,WAAW,CAAC,EAAA;;wBAAhC,SAAS,GAAG,SAAoB;wBACpC,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,WAAA,EAAE,CAAC,CAAC;;;;;KACzB;CACF,CAAC"} \ No newline at end of file diff --git a/routes/locations.ts b/routes/locations.ts new file mode 100644 index 00000000..e9a6843c --- /dev/null +++ b/routes/locations.ts @@ -0,0 +1,8 @@ +import { g } from "$lib/db"; + +export default { + async list(req, res) { + let locations = await g("locations"); + res.send({ locations }); + }, +}; diff --git a/routes/nostr.js b/routes/nostr.js deleted file mode 100644 index fdfa2613..00000000 --- a/routes/nostr.js +++ /dev/null @@ -1,213 +0,0 @@ -import { bail, nada, getUser, uniq } from "$lib/utils"; -import config from "$config"; -import { g, s, db } from "$lib/db"; -import { pool, q } from "$lib/nostr"; - -export default { - async event({ params: { id } }, res) { - let event = await g(`ev:${id}`); - if (!event) return bail(res, "event not found"); - - let { pubkey } = event; - - event.user = (await g(`user:${pubkey}`)) || { - username: pubkey.substr(0, 6), - pubkey, - anon: true, - follows: [], - followers: [], - }; - - res.send(event); - }, - - async notes({ params: { pubkey } }, res) { - let uid = await g(`user:${pubkey}`); - let user = await g(`user:${uid}`); - - 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); - - 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 messages({ params: { pubkey, since = 0 } }, res) { - let params = { - kinds: [4], - authors: [pubkey], - }; - - let opts = { since }; - - await q(`${pubkey}:messages`, params, opts).catch(nada); - - params = { - kinds: [4], - "#p": [pubkey], - }; - - await q(`${pubkey}:messages`, params, opts).catch(nada); - - let messages = await db.sMembers(`${pubkey}:messages`); - - messages = await Promise.all( - messages.map(async (id) => { - let m = await g(`ev:${id}`); - - let aid = await g(`user:${m.pubkey}`); - m.author = await g(`user:${aid}`); - - let rid = await g(`user:${m.tags[0][1]}`); - m.recipient = await g(`user:${rid}`); - - return m; - }), - ); - - res.send(messages); - }, - - async broadcast(req, res) { - let { event } = req.body; - pool.send(["EVENT", event]); - res.send(event); - }, - - async follows({ params: { pubkey }, query: { tagsonly } }, res) { - try { - let sub = `${pubkey}:follows`, - params = { - limit: 1, - kinds: [3], - authors: [pubkey], - }, - opts = { timeout: 60000, eager: 60000 }; - - q(sub, params, opts).catch(nada); - - let tags = (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 uid = await g(`user:${pubkey}`); - let user = await g(`user:${uid}`); - - 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 && a.username.localeCompare(b.username), - ); - - res.send(follows); - } catch (e) { - bail(res, e.message); - } - }, - - async followers({ params: { pubkey } }, res) { - try { - let pubkeys = [ - ...new Set([...(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 uid = await g(`user:${pubkey}`); - let user = await g(`user:${uid}`); - if (!user || !user.updated) { - await q(`${pubkey}:profile:f2`, { - limit: 1, - kinds: [0], - authors: [pubkey], - }).catch(nada); - - uid = await g(`user:${pubkey}`); - user = await g(`user:${uid}`); - } - - 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 && a.username.localeCompare(b.username), - ); - - res.send(followers); - } catch (e) { - console.log(e); - res.code(500).send(e && e.message); - } - }, - - async identities({ query: { name } }, res) { - let names = {}; - if (name) { - names = { [name]: (await getUser(name)).pubkey }; - } else { - let records = await db.sMembers("nip5"); - for (let s of records) { - let [name, pubkey] = s.split(":"); - names[name] = pubkey; - } - } - - res.send({ names }); - }, - - async info(req, res) { - res.send({ pubkey: COINOS_PUBKEY }); - } -}; diff --git a/routes/nostr.js.map b/routes/nostr.js.map new file mode 100644 index 00000000..19e4d7d2 --- /dev/null +++ b/routes/nostr.js.map @@ -0,0 +1 @@ +{"version":3,"file":"nostr.js","sourceRoot":"","sources":["nostr.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/nostr.ts b/routes/nostr.ts new file mode 100644 index 00000000..fdfa2613 --- /dev/null +++ b/routes/nostr.ts @@ -0,0 +1,213 @@ +import { bail, nada, getUser, uniq } from "$lib/utils"; +import config from "$config"; +import { g, s, db } from "$lib/db"; +import { pool, q } from "$lib/nostr"; + +export default { + async event({ params: { id } }, res) { + let event = await g(`ev:${id}`); + if (!event) return bail(res, "event not found"); + + let { pubkey } = event; + + event.user = (await g(`user:${pubkey}`)) || { + username: pubkey.substr(0, 6), + pubkey, + anon: true, + follows: [], + followers: [], + }; + + res.send(event); + }, + + async notes({ params: { pubkey } }, res) { + let uid = await g(`user:${pubkey}`); + let user = await g(`user:${uid}`); + + 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); + + 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 messages({ params: { pubkey, since = 0 } }, res) { + let params = { + kinds: [4], + authors: [pubkey], + }; + + let opts = { since }; + + await q(`${pubkey}:messages`, params, opts).catch(nada); + + params = { + kinds: [4], + "#p": [pubkey], + }; + + await q(`${pubkey}:messages`, params, opts).catch(nada); + + let messages = await db.sMembers(`${pubkey}:messages`); + + messages = await Promise.all( + messages.map(async (id) => { + let m = await g(`ev:${id}`); + + let aid = await g(`user:${m.pubkey}`); + m.author = await g(`user:${aid}`); + + let rid = await g(`user:${m.tags[0][1]}`); + m.recipient = await g(`user:${rid}`); + + return m; + }), + ); + + res.send(messages); + }, + + async broadcast(req, res) { + let { event } = req.body; + pool.send(["EVENT", event]); + res.send(event); + }, + + async follows({ params: { pubkey }, query: { tagsonly } }, res) { + try { + let sub = `${pubkey}:follows`, + params = { + limit: 1, + kinds: [3], + authors: [pubkey], + }, + opts = { timeout: 60000, eager: 60000 }; + + q(sub, params, opts).catch(nada); + + let tags = (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 uid = await g(`user:${pubkey}`); + let user = await g(`user:${uid}`); + + 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 && a.username.localeCompare(b.username), + ); + + res.send(follows); + } catch (e) { + bail(res, e.message); + } + }, + + async followers({ params: { pubkey } }, res) { + try { + let pubkeys = [ + ...new Set([...(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 uid = await g(`user:${pubkey}`); + let user = await g(`user:${uid}`); + if (!user || !user.updated) { + await q(`${pubkey}:profile:f2`, { + limit: 1, + kinds: [0], + authors: [pubkey], + }).catch(nada); + + uid = await g(`user:${pubkey}`); + user = await g(`user:${uid}`); + } + + 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 && a.username.localeCompare(b.username), + ); + + res.send(followers); + } catch (e) { + console.log(e); + res.code(500).send(e && e.message); + } + }, + + async identities({ query: { name } }, res) { + let names = {}; + if (name) { + names = { [name]: (await getUser(name)).pubkey }; + } else { + let records = await db.sMembers("nip5"); + for (let s of records) { + let [name, pubkey] = s.split(":"); + names[name] = pubkey; + } + } + + res.send({ names }); + }, + + async info(req, res) { + res.send({ pubkey: COINOS_PUBKEY }); + } +}; diff --git a/routes/payments.js b/routes/payments.js deleted file mode 100644 index 1ae87916..00000000 --- a/routes/payments.js +++ /dev/null @@ -1,478 +0,0 @@ -import config from "$config"; -import { emit } from "$lib/sockets"; -import { generate } from "$lib/invoices"; -import { v4 } from "uuid"; -import { db, g, s, t } from "$lib/db"; -import { l, err, warn } from "$lib/logging"; -import { bail, fail, getInvoice, btc, sats, SATS, getUser } from "$lib/utils"; -import { requirePin } from "$lib/auth"; -import { - completePayment, - decode, - debit, - credit, - types, - getNode, - build, - sendLightning, - sendOnchain, -} from "$lib/payments"; -import { mqtt1, mqtt2 } from "$lib/mqtt"; -import got from "got"; -import api from "$lib/api"; - -import lq from "$lib/liquid"; -import bc from "$lib/bitcoin"; -import ln from "$lib/ln"; - -export default { - async info({ body, user }, res) { - res.send(await ln.getinfo()); - }, - - async create({ body, user }, res) { - let { amount, hash, maxfee, fund, memo, payreq, rate, username } = body; - let balance = await g(`balance:${user.id}`); - - try { - if (await g("freeze")) fail("Problem sending payment"); - - if (typeof amount !== "undefined") { - amount = parseInt(amount); - if (amount < 0 || amount > SATS || isNaN(amount)) - fail("Invalid amount"); - } - - await requirePin({ body, user }); - - let p; - - if (payreq) { - p = await sendLightning({ user, pr: payreq, amount, maxfee, memo }); - } - - if (!p) { - if (hash) { - p = await debit({ hash, amount, memo, user }); - await credit(hash, amount, memo, user.id); - } else if (fund) { - p = await debit({ hash, amount, memo: fund, user, type: types.fund }); - await db.incrBy(`fund:${fund}`, amount); - await db.lPush(`fund:${fund}:payments`, p.id); - l("funded fund", fund); - } - } - - res.send(p); - } catch (e) { - warn(user.username, "payment failed", amount, balance, hash, payreq); - err(e.message); - bail(res, e.message); - } - }, - - async list({ user: { id }, query: { start, end, limit, offset } }, res) { - if (limit) limit = parseInt(limit); - offset = parseInt(offset) || 0; - - let payments = (await db.lRange(`${id}:payments`, 0, -1)) || []; - payments = ( - await Promise.all( - payments.map(async (id) => { - let p = await g(`payment:${id}`); - if (!p) { - warn("missing payment", id); - return p; - } - if (p.created < start || p.created > end) return; - if (p.type === types.internal) p.with = await g(`user:${p.ref}`); - return p; - }), - ) - ) - .filter((p) => p) - .sort((a, b) => b.created - a.created); - - let count = payments.length; - - let fn = (a, b) => ({ - ...a, - [b.currency]: { - tips: (a[b.currency] ? a[b.currency].tips : 0) + (b.tip || 0), - fiatTips: ( - parseFloat(a[b.currency] ? a[b.currency].fiatTips : 0) + - ((b.tip || 0) * b.rate) / SATS - ).toFixed(2), - sats: - (a[b.currency] ? a[b.currency].sats : 0) + - (b.amount || 0) + - (b.tip || 0) - - (b.fee || 0) - - (b.ourfee || 0), - fiat: ( - parseFloat(a[b.currency] ? a[b.currency].fiat : 0) + - (((b.amount || 0) + - ((b.amount > 0 ? b.tip : -b.tip) || 0) - - (b.fee || 0) - - (b.ourfee || 0)) * - b.rate) / - SATS - ).toFixed(2), - }, - }); - - let incoming = payments.filter((p) => p.amount > 0).reduce(fn, {}); - let outgoing = payments.filter((p) => p.amount < 0).reduce(fn, {}); - - if (limit) payments = payments.slice(offset, offset + limit); - - res.send({ payments, count, incoming, outgoing }); - }, - - async get({ params: { hash } }, res) { - let p = await g(`payment:${hash}`); - if (typeof p === "string") p = await g(`payment:${p}`); - if (p.type === types.internal) p.with = await g(`user:${p.ref}`); - res.send(p); - }, - - async parse({ body: { payreq }, user }, res) { - try { - let hour = 1000 * 60 * 60; - let nodes = await g("nodes"); - let { last } = nodes || {}; - - if (!last || last > Date.now() - hour) { - ({ nodes } = await ln.listnodes()); - nodes.last = Date.now(); - await s("nodes", nodes); - } - - let twoWeeksAgo = new Date(new Date().setDate(new Date().getDate() - 14)); - let decoded = await ln.decodepay(payreq); - let { amount_msat, payee } = decoded; - let node = nodes.find((n) => n.nodeid === payee); - let alias = node ? node.alias : payee.substr(0, 12); - - let amount = Math.round(amount_msat / 1000); - let ourfee = Math.round(amount * config.fee); - let credit = await g(`credit:lightning:${user.id}`); - let covered = Math.min(credit, ourfee) || 0; - ourfee -= covered; - - res.send({ alias, amount, ourfee }); - } catch (e) { - bail(res, e.message); - } - }, - - async fund({ params: { name } }, res) { - let amount = await g(`fund:${name}`); - if (typeof amount === "undefined" || amount === null) - return bail(res, "fund not found"); - let payments = (await db.lRange(`fund:${name}:payments`, 0, -1)) || []; - payments = await Promise.all(payments.map((hash) => g(`payment:${hash}`))); - - await Promise.all( - payments.map(async (p) => (p.user = await g(`user:${p.uid}`))), - ); - - payments = payments.filter((p) => p); - res.send({ amount, payments }); - }, - - async withdraw({ params: { name } }, res) { - let maxWithdrawable = await g(`fund:${name}`); - res.send({ - tag: "withdrawRequest", - callback: `${URL}/lnurlw/${name}`, - k1: name, - defaultDescription: `Withdraw from coinos fund ${name}`, - minWithdrawable: 0, - maxWithdrawable, - }); - }, - - async take({ body: { id, amount, invoice: iid }, user }, res) { - try { - amount = parseInt(amount); - if (amount < 0) fail("Invalid amount"); - - await t(`fund:${id}`, async (balance, db) => { - if (balance < amount) fail("Insufficient funds"); - await db.multi().decrBy(`fund:${id}`, amount).exec(); - }); - - if (!iid) { - iid = v4(); - let rates = await g("rates"); - let { currency } = user; - await s(`invoice:${iid}`, { - currency, - id: iid, - hash: iid, - rate: rates[currency], - uid: user.id, - received: 0, - }); - } - - let payment = await credit(iid, amount, id, id, types.fund); - await db.lPush(`fund:${id}:payments`, payment.id); - - res.send({ payment }); - } catch (e) { - warn("problem withdrawing from fund", e.message); - bail(res, e.message); - } - }, - - async confirm({ body: { txid, wallet, type } }, res) { - try { - let node = getNode(type); - - if (wallet === config.bitcoin.wallet || wallet === config.liquid.wallet) { - let { confirmations, details } = await node.getTransaction(txid); - for (let { address, amount, asset, category, vout } of details) { - if (!address) continue; - if (type === types.liquid && asset !== config.liquid.btc) continue; - if (category === "send") { - let p = await g(`payment:${txid}`); - if (typeof p === "string") p = await g(`payment:${p}`); - if (!p) continue; - - if (confirmations >= 1) p.confirmed = true; - await s(`payment:${p.id}`, p); - emit(p.uid, "payment", p); - continue; - } - - let p = await g(`payment:${txid}:${vout}`); - if (typeof p === "string") p = await g(`payment:${p}`); - - if (!p) { - await credit(address, sats(amount), "", `${txid}:${vout}`, type); - } else if (confirmations >= 1) { - let id = `payment:${txid}:${vout}`; - let p = await g(id); - if (typeof p === "string") p = await g(`payment:${p}`); - if (!p) return db.sAdd("missed", id); - if (p.confirmed) return; - - let invoice = await getInvoice(address); - let { id: iid } = invoice; - - p.confirmed = true; - invoice.received += parseInt(invoice.pending); - invoice.pending = 0; - - l("confirming", id, p.id, p.amount); - - let r = await db - .multi() - .set(`invoice:${iid}`, JSON.stringify(invoice)) - .set(`payment:${p.id}`, JSON.stringify(p)) - .decrBy(`pending:${p.uid}`, p.amount) - .incrBy(`balance:${p.uid}`, p.amount) - .exec(); - - emit(p.uid, "payment", p); - let user = await g(`user:${p.uid}`); - await completePayment(p, user); - } - } - } - res.send({}); - } catch (e) { - console.log(e); - warn(`problem processing ${txid}`); - bail(res, e.message); - } - }, - - async fee({ body, user }, res) { - try { - res.send(await build({ ...body, user })); - } catch (e) { - warn( - "problem estimating fee", - e.message, - user.username, - body.amount, - body.address, - ); - let msg = e.message; - if (msg.includes("500")) msg = ""; - bail(res, "Failed to prepare transaction " + msg); - } - }, - - async send({ body, user }, res) { - try { - await requirePin({ body, user }); - let { hash: txid } = await sendOnchain({ ...body, user }); - let pid = await g(`payment:${txid}`); - let p = await g(`payment:${pid}`); - - res.send({ txid }); - } catch (e) { - warn(user.username, "payment failed", e.message); - res.code(500).send(e.message); - } - }, - - async freeze({ body: { secret } }, res) { - try { - if (secret !== config.adminpass) fail("unauthorized"); - await s("freeze", true); - res.send("ok"); - } catch (e) { - console.log(e); - bail(res, e.message); - } - }, - - async print({ body: { id }, user }, res) { - try { - let p = await g(`payment:${id}`); - if (p.uid !== user.id) fail("unauthorized"); - emit(user.id, "payment", p); - - let { username } = user; - - mqtt1.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, - ); - - mqtt2.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, - ); - - res.send({ ok: true }); - } catch (e) { - bail(res, e.message); - } - }, - - async lnaddress( - { params: { lnaddress, amount, maxfee = 5000 }, body, user }, - res, - ) { - try { - lnaddress = decodeURIComponent(lnaddress); - await requirePin({ body, user }); - - let [username, domain] = lnaddress.split("@"); - let { minSendable, maxSendable, callback, metadata } = await got( - `https://${domain}/.well-known/lnurlp/${username}`, - ).json(); - - let memo = metadata["text/plain"] || ""; - if (amount * 1000 < minSendable || amount * 1000 > maxSendable) - fail("amount out of range"); - - let r = await got(`${callback}?amount=${amount * 1000}`).json(); - if (r.reason) fail(r.rason); - let { pr } = r; - let p = await sendLightning({ user, pr, amount, maxfee, memo }); - - if (!p) { - p = await debit({ hash: pr, amount, memo, user }); - await credit(pr, amount, memo, user.id); - } - - res.send(p); - } catch (e) { - console.log(e); - bail(res, e.message); - } - }, - - async gateway({ body: { short_channel_id, webhook } }, res) { - await s(short_channel_id, webhook); - res.send({ ok: true }); - }, - - async replace({ body: { id }, user }, res) { - try { - let { id: uid } = user; - let p = await g(`payment:${id}`); - if (!p) fail("Payment not found"); - if (p.uid !== user.id) fail("unauthorized"); - - let { tx, type } = await decode(p.hex); - let node = getNode(type); - - let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => - r.json(), - ); - - let outputs = []; - for (let { - scriptPubKey: { address }, - value, - } of tx.vout) { - if (address && !(await node.getAddressInfo(address)).ismine) - outputs.push({ [address]: value }); - } - - let raw = await node.createRawTransaction(tx.vin, outputs); - - let newTx = await node.fundRawTransaction(raw, { - fee_rate: fees.fastestFee + 50, - replaceable: true, - subtractFeeFromOutputs: [], - }); - - let diff = sats(newTx.fee) - p.fee; - if (diff < 0) fail("fee must increase"); - - // let ourfee = Math.round(diff * config.fee) || 0; - // - // ourfee = await db.debit( - // `balance:${uid}`, - // `credit:${type}:${uid}`, - // p.amount || 0, - // 0, - // diff, - // ourfee, - // ); - - if (config[type].walletpass) - await node.walletPassphrase(config[type].walletpass, 300); - p.hex = (await node.signRawTransactionWithWallet(newTx.hex)).hex; - let r = await node.testMempoolAccept([p.hex]); - if (!r[0].allowed) fail(`transaction rejected ${p.hex}`); - warn("bump", user.username, p.hex); - // p.hash = await node.sendRawTransaction(p.hex); - // p.fee = sats(newTx.fee); - // await s(`payment:${id}`, p); - // await s(`payment:${p.hash}`, id); - // emit(uid, "payment", p); - - res.send({ ok: true }); - } catch (e) { - err("failed to bump payment", id, e.message); - bail(res, e.message); - } - }, - - async internal({ body: { username, amount }, user: sender }, res) { - let recipient = await getUser(username); - let inv = await generate({ - invoice: { amount, type: "lightning" }, - user: recipient, - sender, - }); - - let { hash } = inv; - let memo; - - let p = await debit({ hash, amount, memo, user: sender }); - await credit(hash, amount, memo, recipient.id); - - res.send(p); - }, -}; diff --git a/routes/payments.js.map b/routes/payments.js.map new file mode 100644 index 00000000..67fa0d05 --- /dev/null +++ b/routes/payments.js.map @@ -0,0 +1 @@ +{"version":3,"file":"payments.js","sourceRoot":"","sources":["payments.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/payments.ts b/routes/payments.ts new file mode 100644 index 00000000..1ae87916 --- /dev/null +++ b/routes/payments.ts @@ -0,0 +1,478 @@ +import config from "$config"; +import { emit } from "$lib/sockets"; +import { generate } from "$lib/invoices"; +import { v4 } from "uuid"; +import { db, g, s, t } from "$lib/db"; +import { l, err, warn } from "$lib/logging"; +import { bail, fail, getInvoice, btc, sats, SATS, getUser } from "$lib/utils"; +import { requirePin } from "$lib/auth"; +import { + completePayment, + decode, + debit, + credit, + types, + getNode, + build, + sendLightning, + sendOnchain, +} from "$lib/payments"; +import { mqtt1, mqtt2 } from "$lib/mqtt"; +import got from "got"; +import api from "$lib/api"; + +import lq from "$lib/liquid"; +import bc from "$lib/bitcoin"; +import ln from "$lib/ln"; + +export default { + async info({ body, user }, res) { + res.send(await ln.getinfo()); + }, + + async create({ body, user }, res) { + let { amount, hash, maxfee, fund, memo, payreq, rate, username } = body; + let balance = await g(`balance:${user.id}`); + + try { + if (await g("freeze")) fail("Problem sending payment"); + + if (typeof amount !== "undefined") { + amount = parseInt(amount); + if (amount < 0 || amount > SATS || isNaN(amount)) + fail("Invalid amount"); + } + + await requirePin({ body, user }); + + let p; + + if (payreq) { + p = await sendLightning({ user, pr: payreq, amount, maxfee, memo }); + } + + if (!p) { + if (hash) { + p = await debit({ hash, amount, memo, user }); + await credit(hash, amount, memo, user.id); + } else if (fund) { + p = await debit({ hash, amount, memo: fund, user, type: types.fund }); + await db.incrBy(`fund:${fund}`, amount); + await db.lPush(`fund:${fund}:payments`, p.id); + l("funded fund", fund); + } + } + + res.send(p); + } catch (e) { + warn(user.username, "payment failed", amount, balance, hash, payreq); + err(e.message); + bail(res, e.message); + } + }, + + async list({ user: { id }, query: { start, end, limit, offset } }, res) { + if (limit) limit = parseInt(limit); + offset = parseInt(offset) || 0; + + let payments = (await db.lRange(`${id}:payments`, 0, -1)) || []; + payments = ( + await Promise.all( + payments.map(async (id) => { + let p = await g(`payment:${id}`); + if (!p) { + warn("missing payment", id); + return p; + } + if (p.created < start || p.created > end) return; + if (p.type === types.internal) p.with = await g(`user:${p.ref}`); + return p; + }), + ) + ) + .filter((p) => p) + .sort((a, b) => b.created - a.created); + + let count = payments.length; + + let fn = (a, b) => ({ + ...a, + [b.currency]: { + tips: (a[b.currency] ? a[b.currency].tips : 0) + (b.tip || 0), + fiatTips: ( + parseFloat(a[b.currency] ? a[b.currency].fiatTips : 0) + + ((b.tip || 0) * b.rate) / SATS + ).toFixed(2), + sats: + (a[b.currency] ? a[b.currency].sats : 0) + + (b.amount || 0) + + (b.tip || 0) - + (b.fee || 0) - + (b.ourfee || 0), + fiat: ( + parseFloat(a[b.currency] ? a[b.currency].fiat : 0) + + (((b.amount || 0) + + ((b.amount > 0 ? b.tip : -b.tip) || 0) - + (b.fee || 0) - + (b.ourfee || 0)) * + b.rate) / + SATS + ).toFixed(2), + }, + }); + + let incoming = payments.filter((p) => p.amount > 0).reduce(fn, {}); + let outgoing = payments.filter((p) => p.amount < 0).reduce(fn, {}); + + if (limit) payments = payments.slice(offset, offset + limit); + + res.send({ payments, count, incoming, outgoing }); + }, + + async get({ params: { hash } }, res) { + let p = await g(`payment:${hash}`); + if (typeof p === "string") p = await g(`payment:${p}`); + if (p.type === types.internal) p.with = await g(`user:${p.ref}`); + res.send(p); + }, + + async parse({ body: { payreq }, user }, res) { + try { + let hour = 1000 * 60 * 60; + let nodes = await g("nodes"); + let { last } = nodes || {}; + + if (!last || last > Date.now() - hour) { + ({ nodes } = await ln.listnodes()); + nodes.last = Date.now(); + await s("nodes", nodes); + } + + let twoWeeksAgo = new Date(new Date().setDate(new Date().getDate() - 14)); + let decoded = await ln.decodepay(payreq); + let { amount_msat, payee } = decoded; + let node = nodes.find((n) => n.nodeid === payee); + let alias = node ? node.alias : payee.substr(0, 12); + + let amount = Math.round(amount_msat / 1000); + let ourfee = Math.round(amount * config.fee); + let credit = await g(`credit:lightning:${user.id}`); + let covered = Math.min(credit, ourfee) || 0; + ourfee -= covered; + + res.send({ alias, amount, ourfee }); + } catch (e) { + bail(res, e.message); + } + }, + + async fund({ params: { name } }, res) { + let amount = await g(`fund:${name}`); + if (typeof amount === "undefined" || amount === null) + return bail(res, "fund not found"); + let payments = (await db.lRange(`fund:${name}:payments`, 0, -1)) || []; + payments = await Promise.all(payments.map((hash) => g(`payment:${hash}`))); + + await Promise.all( + payments.map(async (p) => (p.user = await g(`user:${p.uid}`))), + ); + + payments = payments.filter((p) => p); + res.send({ amount, payments }); + }, + + async withdraw({ params: { name } }, res) { + let maxWithdrawable = await g(`fund:${name}`); + res.send({ + tag: "withdrawRequest", + callback: `${URL}/lnurlw/${name}`, + k1: name, + defaultDescription: `Withdraw from coinos fund ${name}`, + minWithdrawable: 0, + maxWithdrawable, + }); + }, + + async take({ body: { id, amount, invoice: iid }, user }, res) { + try { + amount = parseInt(amount); + if (amount < 0) fail("Invalid amount"); + + await t(`fund:${id}`, async (balance, db) => { + if (balance < amount) fail("Insufficient funds"); + await db.multi().decrBy(`fund:${id}`, amount).exec(); + }); + + if (!iid) { + iid = v4(); + let rates = await g("rates"); + let { currency } = user; + await s(`invoice:${iid}`, { + currency, + id: iid, + hash: iid, + rate: rates[currency], + uid: user.id, + received: 0, + }); + } + + let payment = await credit(iid, amount, id, id, types.fund); + await db.lPush(`fund:${id}:payments`, payment.id); + + res.send({ payment }); + } catch (e) { + warn("problem withdrawing from fund", e.message); + bail(res, e.message); + } + }, + + async confirm({ body: { txid, wallet, type } }, res) { + try { + let node = getNode(type); + + if (wallet === config.bitcoin.wallet || wallet === config.liquid.wallet) { + let { confirmations, details } = await node.getTransaction(txid); + for (let { address, amount, asset, category, vout } of details) { + if (!address) continue; + if (type === types.liquid && asset !== config.liquid.btc) continue; + if (category === "send") { + let p = await g(`payment:${txid}`); + if (typeof p === "string") p = await g(`payment:${p}`); + if (!p) continue; + + if (confirmations >= 1) p.confirmed = true; + await s(`payment:${p.id}`, p); + emit(p.uid, "payment", p); + continue; + } + + let p = await g(`payment:${txid}:${vout}`); + if (typeof p === "string") p = await g(`payment:${p}`); + + if (!p) { + await credit(address, sats(amount), "", `${txid}:${vout}`, type); + } else if (confirmations >= 1) { + let id = `payment:${txid}:${vout}`; + let p = await g(id); + if (typeof p === "string") p = await g(`payment:${p}`); + if (!p) return db.sAdd("missed", id); + if (p.confirmed) return; + + let invoice = await getInvoice(address); + let { id: iid } = invoice; + + p.confirmed = true; + invoice.received += parseInt(invoice.pending); + invoice.pending = 0; + + l("confirming", id, p.id, p.amount); + + let r = await db + .multi() + .set(`invoice:${iid}`, JSON.stringify(invoice)) + .set(`payment:${p.id}`, JSON.stringify(p)) + .decrBy(`pending:${p.uid}`, p.amount) + .incrBy(`balance:${p.uid}`, p.amount) + .exec(); + + emit(p.uid, "payment", p); + let user = await g(`user:${p.uid}`); + await completePayment(p, user); + } + } + } + res.send({}); + } catch (e) { + console.log(e); + warn(`problem processing ${txid}`); + bail(res, e.message); + } + }, + + async fee({ body, user }, res) { + try { + res.send(await build({ ...body, user })); + } catch (e) { + warn( + "problem estimating fee", + e.message, + user.username, + body.amount, + body.address, + ); + let msg = e.message; + if (msg.includes("500")) msg = ""; + bail(res, "Failed to prepare transaction " + msg); + } + }, + + async send({ body, user }, res) { + try { + await requirePin({ body, user }); + let { hash: txid } = await sendOnchain({ ...body, user }); + let pid = await g(`payment:${txid}`); + let p = await g(`payment:${pid}`); + + res.send({ txid }); + } catch (e) { + warn(user.username, "payment failed", e.message); + res.code(500).send(e.message); + } + }, + + async freeze({ body: { secret } }, res) { + try { + if (secret !== config.adminpass) fail("unauthorized"); + await s("freeze", true); + res.send("ok"); + } catch (e) { + console.log(e); + bail(res, e.message); + } + }, + + async print({ body: { id }, user }, res) { + try { + let p = await g(`payment:${id}`); + if (p.uid !== user.id) fail("unauthorized"); + emit(user.id, "payment", p); + + let { username } = user; + + mqtt1.publish( + username, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, + ); + + mqtt2.publish( + username, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, + ); + + res.send({ ok: true }); + } catch (e) { + bail(res, e.message); + } + }, + + async lnaddress( + { params: { lnaddress, amount, maxfee = 5000 }, body, user }, + res, + ) { + try { + lnaddress = decodeURIComponent(lnaddress); + await requirePin({ body, user }); + + let [username, domain] = lnaddress.split("@"); + let { minSendable, maxSendable, callback, metadata } = await got( + `https://${domain}/.well-known/lnurlp/${username}`, + ).json(); + + let memo = metadata["text/plain"] || ""; + if (amount * 1000 < minSendable || amount * 1000 > maxSendable) + fail("amount out of range"); + + let r = await got(`${callback}?amount=${amount * 1000}`).json(); + if (r.reason) fail(r.rason); + let { pr } = r; + let p = await sendLightning({ user, pr, amount, maxfee, memo }); + + if (!p) { + p = await debit({ hash: pr, amount, memo, user }); + await credit(pr, amount, memo, user.id); + } + + res.send(p); + } catch (e) { + console.log(e); + bail(res, e.message); + } + }, + + async gateway({ body: { short_channel_id, webhook } }, res) { + await s(short_channel_id, webhook); + res.send({ ok: true }); + }, + + async replace({ body: { id }, user }, res) { + try { + let { id: uid } = user; + let p = await g(`payment:${id}`); + if (!p) fail("Payment not found"); + if (p.uid !== user.id) fail("unauthorized"); + + let { tx, type } = await decode(p.hex); + let node = getNode(type); + + let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => + r.json(), + ); + + let outputs = []; + for (let { + scriptPubKey: { address }, + value, + } of tx.vout) { + if (address && !(await node.getAddressInfo(address)).ismine) + outputs.push({ [address]: value }); + } + + let raw = await node.createRawTransaction(tx.vin, outputs); + + let newTx = await node.fundRawTransaction(raw, { + fee_rate: fees.fastestFee + 50, + replaceable: true, + subtractFeeFromOutputs: [], + }); + + let diff = sats(newTx.fee) - p.fee; + if (diff < 0) fail("fee must increase"); + + // let ourfee = Math.round(diff * config.fee) || 0; + // + // ourfee = await db.debit( + // `balance:${uid}`, + // `credit:${type}:${uid}`, + // p.amount || 0, + // 0, + // diff, + // ourfee, + // ); + + if (config[type].walletpass) + await node.walletPassphrase(config[type].walletpass, 300); + p.hex = (await node.signRawTransactionWithWallet(newTx.hex)).hex; + let r = await node.testMempoolAccept([p.hex]); + if (!r[0].allowed) fail(`transaction rejected ${p.hex}`); + warn("bump", user.username, p.hex); + // p.hash = await node.sendRawTransaction(p.hex); + // p.fee = sats(newTx.fee); + // await s(`payment:${id}`, p); + // await s(`payment:${p.hash}`, id); + // emit(uid, "payment", p); + + res.send({ ok: true }); + } catch (e) { + err("failed to bump payment", id, e.message); + bail(res, e.message); + } + }, + + async internal({ body: { username, amount }, user: sender }, res) { + let recipient = await getUser(username); + let inv = await generate({ + invoice: { amount, type: "lightning" }, + user: recipient, + sender, + }); + + let { hash } = inv; + let memo; + + let p = await debit({ hash, amount, memo, user: sender }); + await credit(hash, amount, memo, recipient.id); + + res.send(p); + }, +}; diff --git a/routes/rates.js b/routes/rates.js deleted file mode 100644 index 828e5b2c..00000000 --- a/routes/rates.js +++ /dev/null @@ -1,11 +0,0 @@ -import { g } from "$lib/db"; - -export default { - async last(req, res) { - res.send(await g('rate')); - }, - - async index(req, res) { - res.send(await g('rates')); - }, -}; diff --git a/routes/rates.js.map b/routes/rates.js.map new file mode 100644 index 00000000..1df70b10 --- /dev/null +++ b/routes/rates.js.map @@ -0,0 +1 @@ +{"version":3,"file":"rates.js","sourceRoot":"","sources":["rates.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8BAA4B;AAE5B,kBAAe;IACP,IAAI,YAAC,GAAG,EAAE,GAAG;;;;;;wBACjB,KAAA,CAAA,KAAA,GAAG,CAAA,CAAC,IAAI,CAAA;wBAAC,qBAAM,IAAA,MAAC,EAAC,MAAM,CAAC,EAAA;;wBAAxB,cAAS,SAAe,EAAC,CAAC;;;;;KAC3B;IAEK,KAAK,YAAC,GAAG,EAAE,GAAG;;;;;;wBAClB,KAAA,CAAA,KAAA,GAAG,CAAA,CAAC,IAAI,CAAA;wBAAC,qBAAM,IAAA,MAAC,EAAC,OAAO,CAAC,EAAA;;wBAAzB,cAAS,SAAgB,EAAC,CAAC;;;;;KAC5B;CACF,CAAC"} \ No newline at end of file diff --git a/routes/rates.ts b/routes/rates.ts new file mode 100644 index 00000000..828e5b2c --- /dev/null +++ b/routes/rates.ts @@ -0,0 +1,11 @@ +import { g } from "$lib/db"; + +export default { + async last(req, res) { + res.send(await g('rate')); + }, + + async index(req, res) { + res.send(await g('rates')); + }, +}; diff --git a/routes/requests.js b/routes/requests.js deleted file mode 100644 index a0d55739..00000000 --- a/routes/requests.js +++ /dev/null @@ -1,85 +0,0 @@ -import { fail } from "$lib/utils"; -import { emit } from "$lib/sockets"; -import { g, s, db } from "$lib/db"; -import { v4 } from "uuid"; - -export default { - async get({ params: { id } }, res) { - let request = await g(`request:${id}`); - if (!request) fail("request not found"); - request.recipient = await g(`user:${request.recipient_id}`); - request.requester = await g(`user:${request.requester_id}`); - request.invoice = await g(`invoice:${request.invoice_id}`); - res.send({ request }); - }, - - async list({ user: { id } }, res) { - try { - const day = new Date(new Date().setDate(new Date().getDate() - 1)); - - let requests = await db.lRange(`${id}:requests`, 0, -1); - - requests = await Promise.all(requests.map((id) => g(`request:${id}`))); - - requests = await Promise.all( - requests.map(async (r) => { - r.requester = await g(`user:${r.requester_id}`); - r.recipient = await g(`user:${r.recipient_id}`); - return r; - }), - ); - - let sent = requests.filter((r) => r.requester_id === id); - - let received = requests - .filter((r) => r.recipient_id === id) - .filter((r) => !r.invoice_id); - - let invoices = []; - for (let r of sent) { - if (!r.invoice_id) continue; - - let invoice = await g(`invoice:${r.invoice_id}`); - if (!invoice) continue; - - invoice.request = r; - if (invoice.amount < invoice.received) invoices.push(invoice); - } - - res.send({ invoices, sent, received }); - } catch (e) { - console.log(e); - res.code(500).send(e.message); - } - }, - - async create( - { - body: { recipient, ...params }, - user: { id: requester_id, username, profile }, - }, - res, - ) { - let recipient_id = await g(`user:${recipient}`); - - let id = v4(); - let request = { - id, - recipient_id, - requester_id, - ...params, - }; - - await s(`request:${id}`, request); - await db.lPush(`${requester_id}:requests`, id); - await db.lPush(`${recipient_id}:requests`, id); - - emit(recipient, "request", request); - res.send(request); - }, - - async destroy({ body: { request_id }, user: { id } }, res) { - await db.lrem(`user:${id}:requests`, request_id); - res.send(); - }, -}; diff --git a/routes/requests.js.map b/routes/requests.js.map new file mode 100644 index 00000000..448a576e --- /dev/null +++ b/routes/requests.js.map @@ -0,0 +1 @@ +{"version":3,"file":"requests.js","sourceRoot":"","sources":["requests.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/requests.ts b/routes/requests.ts new file mode 100644 index 00000000..a0d55739 --- /dev/null +++ b/routes/requests.ts @@ -0,0 +1,85 @@ +import { fail } from "$lib/utils"; +import { emit } from "$lib/sockets"; +import { g, s, db } from "$lib/db"; +import { v4 } from "uuid"; + +export default { + async get({ params: { id } }, res) { + let request = await g(`request:${id}`); + if (!request) fail("request not found"); + request.recipient = await g(`user:${request.recipient_id}`); + request.requester = await g(`user:${request.requester_id}`); + request.invoice = await g(`invoice:${request.invoice_id}`); + res.send({ request }); + }, + + async list({ user: { id } }, res) { + try { + const day = new Date(new Date().setDate(new Date().getDate() - 1)); + + let requests = await db.lRange(`${id}:requests`, 0, -1); + + requests = await Promise.all(requests.map((id) => g(`request:${id}`))); + + requests = await Promise.all( + requests.map(async (r) => { + r.requester = await g(`user:${r.requester_id}`); + r.recipient = await g(`user:${r.recipient_id}`); + return r; + }), + ); + + let sent = requests.filter((r) => r.requester_id === id); + + let received = requests + .filter((r) => r.recipient_id === id) + .filter((r) => !r.invoice_id); + + let invoices = []; + for (let r of sent) { + if (!r.invoice_id) continue; + + let invoice = await g(`invoice:${r.invoice_id}`); + if (!invoice) continue; + + invoice.request = r; + if (invoice.amount < invoice.received) invoices.push(invoice); + } + + res.send({ invoices, sent, received }); + } catch (e) { + console.log(e); + res.code(500).send(e.message); + } + }, + + async create( + { + body: { recipient, ...params }, + user: { id: requester_id, username, profile }, + }, + res, + ) { + let recipient_id = await g(`user:${recipient}`); + + let id = v4(); + let request = { + id, + recipient_id, + requester_id, + ...params, + }; + + await s(`request:${id}`, request); + await db.lPush(`${requester_id}:requests`, id); + await db.lPush(`${recipient_id}:requests`, id); + + emit(recipient, "request", request); + res.send(request); + }, + + async destroy({ body: { request_id }, user: { id } }, res) { + await db.lrem(`user:${id}:requests`, request_id); + res.send(); + }, +}; diff --git a/routes/shopify.js b/routes/shopify.js deleted file mode 100644 index bcb60ea3..00000000 --- a/routes/shopify.js +++ /dev/null @@ -1,41 +0,0 @@ -import got from "got"; -import { g } from "$lib/db"; -import { err } from "$lib/logging"; - -let query = `mutation orderMarkAsPaid($input: OrderMarkAsPaidInput!) { - orderMarkAsPaid(input: $input) { - order { id } - userErrors { - field - message - } - } -}`; - -export default async ({ body: { hash }, params: { id } }, res) => { - let p = await g(`payment:${hash}`); - if (typeof p === "string") p = await g(`payment:${p}`); - let user = await g(`user:${p.uid}`); - - try { - let r = await got - .post( - `https://${user.shopifyStore}.myshopify.com/admin/api/2023-07/graphql.json`, - { - headers: { - "Content-Type": "application/json", - "X-Shopify-Access-Token": user.shopifyToken, - }, - json: { - query, - variables: { input: { id: `gid://shopify/Order/${id}` } }, - }, - }, - ) - .json(); - - res.send(r); - } catch (e) { - err("problem marking shopify order as paid", e.message); - } -}; diff --git a/routes/shopify.js.map b/routes/shopify.js.map new file mode 100644 index 00000000..af2690f8 --- /dev/null +++ b/routes/shopify.js.map @@ -0,0 +1 @@ +{"version":3,"file":"shopify.js","sourceRoot":"","sources":["shopify.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4CAAsB;AACtB,8BAA4B;AAC5B,wCAAmC;AAEnC,IAAI,KAAK,GAAG,kLAQV,CAAC;AAEH,mBAAe,+EAAO,EAAkC,EAAE,GAAG;;QAA7B,IAAI,eAAA,EAAc,EAAE,eAAA;;;oBAC1C,qBAAM,IAAA,MAAC,EAAC,kBAAW,IAAI,CAAE,CAAC,EAAA;;gBAA9B,CAAC,GAAG,SAA0B;qBAC9B,CAAA,OAAO,CAAC,KAAK,QAAQ,CAAA,EAArB,wBAAqB;gBAAM,qBAAM,IAAA,MAAC,EAAC,kBAAW,CAAC,CAAE,CAAC,EAAA;;gBAA3B,CAAC,GAAG,SAAuB,CAAC;;oBAC5C,qBAAM,IAAA,MAAC,EAAC,eAAQ,CAAC,CAAC,GAAG,CAAE,CAAC,EAAA;;gBAA/B,IAAI,GAAG,SAAwB;;;;gBAGzB,qBAAM,aAAG;yBACd,IAAI,CACH,kBAAW,IAAI,CAAC,YAAY,kDAA+C,EAC3E;wBACE,OAAO,EAAE;4BACP,cAAc,EAAE,kBAAkB;4BAClC,wBAAwB,EAAE,IAAI,CAAC,YAAY;yBAC5C;wBACD,IAAI,EAAE;4BACJ,KAAK,OAAA;4BACL,SAAS,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,8BAAuB,EAAE,CAAE,EAAE,EAAE;yBAC1D;qBACF,CACF;yBACA,IAAI,EAAE,EAAA;;gBAdL,CAAC,GAAG,SAcC;gBAET,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;;;gBAEZ,IAAA,aAAG,EAAC,uCAAuC,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;KAE3D,EAAC"} \ No newline at end of file diff --git a/routes/shopify.ts b/routes/shopify.ts new file mode 100644 index 00000000..bcb60ea3 --- /dev/null +++ b/routes/shopify.ts @@ -0,0 +1,41 @@ +import got from "got"; +import { g } from "$lib/db"; +import { err } from "$lib/logging"; + +let query = `mutation orderMarkAsPaid($input: OrderMarkAsPaidInput!) { + orderMarkAsPaid(input: $input) { + order { id } + userErrors { + field + message + } + } +}`; + +export default async ({ body: { hash }, params: { id } }, res) => { + let p = await g(`payment:${hash}`); + if (typeof p === "string") p = await g(`payment:${p}`); + let user = await g(`user:${p.uid}`); + + try { + let r = await got + .post( + `https://${user.shopifyStore}.myshopify.com/admin/api/2023-07/graphql.json`, + { + headers: { + "Content-Type": "application/json", + "X-Shopify-Access-Token": user.shopifyToken, + }, + json: { + query, + variables: { input: { id: `gid://shopify/Order/${id}` } }, + }, + }, + ) + .json(); + + res.send(r); + } catch (e) { + err("problem marking shopify order as paid", e.message); + } +}; diff --git a/routes/users.js b/routes/users.js deleted file mode 100644 index 17bea006..00000000 --- a/routes/users.js +++ /dev/null @@ -1,556 +0,0 @@ -import { g, s, db } from "$lib/db"; -import config from "$config"; -import store from "$lib/store"; -import { - fields, - nada, - pick, - uniq, - wait, - bail, - fail, - getUser, -} from "$lib/utils"; -import jwt from "jsonwebtoken"; -import { authenticator } from "otplib"; -import whitelist from "$lib/whitelist"; -import { l, err, warn } from "$lib/logging"; -import { emit } from "$lib/sockets"; -import register from "$lib/register"; -import { requirePin } from "$lib/auth"; -import { v4 } from "uuid"; -import { parseISO } from "date-fns"; -import { types } from "$lib/payments"; -import { bech32 } from "bech32"; -import { mail, templates } from "$lib/mail"; - -import got from "got"; -import upload from "$lib/upload"; - -const { encode, decode, fromWords, toWords } = bech32; - -export default { - upload, - - async me({ user }, res) { - try { - user.balance = await g(`balance:${user.id}`); - user.prompt = !!user.prompt; - if (user.pubkey) - user.npub = encode("npub", toWords(Buffer.from(user.pubkey, "hex"))); - - res.send(pick(user, whitelist)); - } catch (e) { - console.log("problem fetching user", e); - res.code(500).send(e.message); - } - }, - - async list({ user }, res) { - if (!user.admin) fail("unauthorized"); - - let users = []; - - for await (let k of db.scanIterator({ MATCH: "balance:*" })) { - let uid = k.split(":")[1]; - let user = await getUser(uid); - - if (!user) { - await db.del(`balance:${uid}`); - continue; - } - - user.balance = await g(k); - - let payments = await db.lRange(`${uid}:payments`, 0, -1); - - let total = 0; - for (let pid of payments) { - let p = await g(`payment:${pid}`); - if (!p) continue; - total += p.amount; - if (p.amount < 0) - total -= (p.fee || 0) + (p.ourfee || 0) + (p.tip || 0); - else total += p.tip || 0; - } - - user.expected = total; - users.push(user); - } - - res.send(users); - }, - - async get({ params: { key } }, res) { - key = key.toLowerCase().replace(/\s/g, ""); - try { - if (key.startsWith("npub")) { - try { - key = Buffer.from(fromWords(decode(key).words)).toString("hex"); - } catch (e) {} - } - - let user = await getUser(key); - - if (!user && key.length === 64) { - user = { - currency: "USD", - username: key, - display: key.substr(0, 6), - pubkey: key, - anon: true, - }; - } - - if (!user) return res.code(500).send("User not found"); - - let whitelist = [ - "address", - "anon", - "banner", - "banner", - "currency", - "display", - "id", - "hidepay", - "memoPrompt", - "npub", - "profile", - "prompt", - "pubkey", - "username", - ]; - - if (user.pubkey) - user.npub = encode("npub", toWords(Buffer.from(user.pubkey, "hex"))); - user.prompt = !!user.prompt; - - res.send(pick(user, whitelist)); - } catch (e) { - err("problem getting user", e.message); - res.code(500).send(e.message); - } - }, - - async create({ body, headers }, res) { - try { - const ip = headers["cf-connecting-ip"]; - if (!body.user) fail("no user object provided"); - let { user } = body; - let fields = [ - "profile", - "cipher", - "pubkey", - "password", - "username", - "salt", - ]; - - user = await register(pick(user, fields), ip, false); - l("registered new user", user.username); - res.send(pick(user, whitelist)); - } catch (e) { - res.code(500).send(e.message); - } - }, - - 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"); - } - - user.twofa = false; - await s(`user:${id}`, user); - emit(username, "user", user); - emit(username, "otpsecret", user.otpsecret); - l("disabled 2fa", username); - res.send({}); - }, - - 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"); - } - - l("enabled 2fa", username); - res.send({}); - }, - - async update({ user, body }, res) { - try { - l("updating user", user.username); - - let { - profile, - banner, - confirm, - password, - pin, - newpin, - pubkey, - username, - shopifyToken, - shopifyStore, - } = body; - - if (user.pin && !(pin === user.pin)) fail("Pin required"); - if (typeof newpin !== "undefined" && newpin.length === 6) - user.pin = newpin; - if (user.pin === "delete") delete user.pin; - - let exists; - if (username) exists = await getUser(username); - - let token, oldname; - if (user.username.toLowerCase() !== username.toLowerCase() && exists) { - err("username taken", username, user.username, exists.username); - fail("Username taken"); - } else if (username) { - if (user.username.toLowerCase() !== username.toLowerCase()) - l("changing username", user.username, username); - - await db.del(`user:${user.username}`); - oldname = user.username; - user.username = username; - } - - if (pubkey) exists = await getUser(pubkey); - if (exists && ![oldname, username].includes(exists.username)) { - warn("key in use", pubkey, exists.username); - if (exists.anon) await db.del(`user:${pubkey}`); - else fail("Key in use by another account"); - } - - let attributes = [ - "address", - "autowithdraw", - "banner", - "cipher", - "currencies", - "currency", - "destination", - "display", - "email", - "language", - "fiat", - "locktime", - "memoPrompt", - "nip5", - "notify", - "nsec", - "prompt", - "profile", - "pubkey", - "reserve", - "salt", - "seed", - "threshold", - "tokens", - "twofa", - "shopifyToken", - "shopifyStore", - ]; - - for (let a of attributes) { - if (typeof body[a] !== "undefined") user[a] = body[a]; - } - - if (password && password === confirm) { - user.password = await Bun.password.hash(password, { - algorithm: "bcrypt", - cost: 1, - }); - } - - user.haspin = !!user.pin; - await s(`user:${user.pubkey}`, user.id); - await s( - `user:${user.username.toLowerCase().replace(/\s/g, "")}`, - user.id, - ); - - await s(`user:${user.id}`, user); - if (user.nip5) await db.sAdd("nip5", `${user.username}:${user.pubkey}`); - - emit(user.id, "user", user); - res.send({ user, token }); - } catch (e) { - warn("failed to update", user.username, e.message); - bail(res, e.message); - } - }, - - async login(req, res) { - try { - let { username, password, token: twofa } = req.body; - - if (username !== "coinos" && username !== "funk") - l("logging in", username, req.headers["cf-connecting-ip"]); - - username = username.toLowerCase().replace(/\s/g, ""); - let user = await getUser(username); - - if ( - !user || - (user.password && - !( - (config.adminpass && password === config.adminpass) || - (await Bun.password.verify(password, user.password)) - )) - ) { - warn("invalid username or password attempt", username); - return res.code(401).send({}); - } - - if ( - !(config.adminpass && password === config.adminpass) && - user.twofa && - (typeof twofa === "undefined" || - !authenticator.check(twofa, user.otpsecret)) - ) { - return res.code(401).send("2fa required"); - } - - if (username !== "coinos" && username !== "funk") - l("logged in", username); - - let payload = { 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) { - err("login error", e.message, req.socket.remoteAddress); - res.code(401).send({}); - } - }, - - 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); - - try { - if (!password) fail("password not provided"); - res.send(await Bun.password.verify(password, user.password)); - } catch (e) { - bail(res, e.message); - } - }, - - async pin({ body: { pin }, user }, res) { - res.send(!user.pin || user.pin === pin); - }, - - 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); - } - }, - - async contacts({ user: { id } }, res) { - let lastlen = (await g(`${id}:lastlen`)) || 0; - let len = await db.lLen(`${id}:payments`); - let payments = (await db.lRange(`${id}:payments`, 0, len - lastlen)) || []; - await db.set(`${id}:lastlen`, len); - - let contacts = (await g(`${id}:contacts`)) || []; - - for (let { ref } of ( - await Promise.all( - payments.reverse().map(async (id) => await g(`payment:${id}`)), - ) - ).filter((p) => p && p.type === types.internal && p.ref)) { - let i = contacts.findIndex((c) => c && c.id === ref); - if (~i) contacts.splice(i, 1); - let u = await g(`user:${ref}`); - if (typeof u === "string") u = await g(`user:${ref}`); - if (u) contacts.unshift(pick(u, ["id", "profile", "username"])); - } - - await s(`${id}:contacts`, contacts); - - res.send(contacts); - }, - - async del({ params: { username }, headers: { authorization } }, res) { - username = username.toLowerCase(); - if (!(authorization && authorization.includes(config.admin))) - return res.code(401).send("unauthorized"); - - let { id, pubkey } = await g( - `user:${await g(`user:${username.replace(/\s/g, "").toLowerCase()}`)}`, - ); - let invoices = await db.lRange(`${id}:invoices`, 0, -1); - let payments = await db.lRange(`${id}:payments`, 0, -1); - - for (let { id } of invoices) db.del(`invoice:${id}`); - for (let { id } of payments) db.del(`payment:${id}`); - db.del(`user:${username.toLowerCase()}`); - db.del(`user:${id}`); - db.del(`user:${pubkey}`); - - res.send({}); - }, - - async reset({ body: { code, username, password }, user: u }, res) { - let admin = u && u.admin; - let id, user; - - if (admin) { - id = await g(`user:${username.toLowerCase().replace(/\s/g, "")}`); - user = await g(`user:${id}`); - } else { - id = await g(`reset:${code}`); - user = await g(`user:${id}`); - } - - if (!user) fail("user not found"); - - try { - user.pin = null; - user.pubkey = null; - user.cipher = null; - user.salt = null; - user.password = await Bun.password.hash(password, { - algorithm: "bcrypt", - cost: 1, - }); - - await s(`user:${id}`, user); - await db.del(`reset:${code}`); - - res.send(pick(user, whitelist)); - } catch (e) { - bail(res, e.message); - } - }, - - async printerlogin({ body: { username, topic } }, res) { - if (username === topic) res.send({ ok: true }); - else bail(res, "unauthorized"); - }, - - async acl({ body: { username, topic } }, res) { - if (username === topic) res.send({ ok: true }); - else bail(res, "unauthorized"); - }, - - async superuser({ body: { username } }, res) { - if (username === config.mqtt2.username) res.send({ ok: true }); - else bail(res, "unauthorized"); - }, - - async request({ body: { id, email } }, res) { - try { - let user = await g(`user:${id}`); - - if (!user) fail("user not found"); - if (await g(`email:${email.toLowerCase()}`)) fail("Email already in use"); - - let { username } = user; - - if (email !== user.email) { - user.verified = false; - await s(`user:${id}`, user); - user.email = email; - } - - let code = v4(); - await s(`verify:${code}`, { id, email }); - let link = `${process.env.URL}/${username}/verify/${code}`; - let subject = "Email Verification"; - - await mail(user, subject, templates.verifyEmail, { - username, - link, - }); - - res.send({ ok: true }); - } catch (e) { - bail(res, e.message); - } - }, - - async verify({ params: { code } }, res) { - try { - let { id, email } = await g(`verify:${code}`); - if (!id) fail(res, "verification failed"); - let user = await g(`user:${id}`); - user.email = email; - user.verified = true; - await s(`user:${id}`, user); - await s(`email:${email.toLowerCase()}`, id); - - res.send(pick(user, whitelist)); - } catch (e) { - bail(res, e.message); - } - }, - - async forgot({ body: { email } }, res) { - try { - let uid = await g(`email:${email.toLowerCase()}`); - let user = await g(`user:${uid}`); - - if (user) { - let code = v4(); - let link = `${process.env.URL}/reset/${code}`; - await s(`reset:${code}`, uid); - - await mail(user, "Password reset", templates.passwordReset, { - ...user, - link, - }); - } - - res.send({}); - } catch (e) { - bail(res, e.message); - } - }, - - async items({ params: { id } }, res) { - let item = await g(`item:${id}`); - return item; - }, - - async hidepay({ body: { username } }, res) { - let u = await getUser(username); - u.hidepay = true; - await s(`user:${u.id}`, u); - res.send({}); - }, - - async unlimit({ body: { username } }, res) { - let u = await getUser(username); - u.unlimited = true; - await s(`user:${u.id}`, u); - res.send({}); - }, -}; diff --git a/routes/users.js.map b/routes/users.js.map new file mode 100644 index 00000000..75a073fb --- /dev/null +++ b/routes/users.js.map @@ -0,0 +1 @@ +{"version":3,"file":"users.js","sourceRoot":"","sources":["users.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8BAAmC;AACnC,oDAA6B;AAE7B,oCASoB;AACpB,8DAA+B;AAC/B,iCAAuC;AACvC,6DAAuC;AACvC,wCAA4C;AAC5C,wCAAoC;AACpC,2DAAqC;AACrC,kCAAuC;AACvC,6BAA0B;AAE1B,0CAAsC;AACtC,iCAAgC;AAChC,kCAA4C;AAG5C,uDAAiC;AAEzB,IAAA,MAAM,GAAiC,eAAM,OAAvC,EAAE,MAAM,GAAyB,eAAM,OAA/B,EAAE,SAAS,GAAc,eAAM,UAApB,EAAE,OAAO,GAAK,eAAM,QAAX,CAAY;AAEtD,kBAAe;IACb,MAAM,kBAAA;IAEA,EAAE;4DAAC,EAAQ,EAAE,GAAG;;gBAAX,IAAI,UAAA;;;;;wBAEX,KAAA,IAAI,CAAA;wBAAW,qBAAM,IAAA,MAAC,EAAC,kBAAW,IAAI,CAAC,EAAE,CAAE,CAAC,EAAA;;wBAA5C,GAAK,OAAO,GAAG,SAA6B,CAAC;wBAC7C,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;wBAC5B,IAAI,IAAI,CAAC,MAAM;4BACb,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;wBAEvE,GAAG,CAAC,IAAI,CAAC,IAAA,YAAI,EAAC,IAAI,EAAE,mBAAS,CAAC,CAAC,CAAC;;;;wBAEhC,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,GAAC,CAAC,CAAC;wBACxC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAEjC;IAEK,IAAI;4DAAC,EAAQ,EAAE,GAAG;;;gBAAX,IAAI,UAAA;;;;wBACf,IAAI,CAAC,IAAI,CAAC,KAAK;4BAAE,IAAA,YAAI,EAAC,cAAc,CAAC,CAAC;wBAElC,KAAK,GAAG,EAAE,CAAC;;;;mCAEK,KAAA,cAAA,OAAE,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;;;;;wBAAvC,cAAuC;wBAAvC,WAAuC;wBAA5C,CAAC,KAAA,CAAA;wBACV,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;wBACf,qBAAM,IAAA,eAAO,EAAC,GAAG,CAAC,EAAA;;wBAAzB,SAAO,SAAkB;6BAEzB,CAAC,MAAI,EAAL,wBAAK;wBACP,qBAAM,OAAE,CAAC,GAAG,CAAC,kBAAW,GAAG,CAAE,CAAC,EAAA;;wBAA9B,SAA8B,CAAC;wBAC/B,yBAAS;;wBAGX,KAAA,MAAI,CAAA;wBAAW,qBAAM,IAAA,MAAC,EAAC,CAAC,CAAC,EAAA;;wBAAzB,GAAK,OAAO,GAAG,SAAU,CAAC;wBAEX,qBAAM,OAAE,CAAC,MAAM,CAAC,UAAG,GAAG,cAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAA;;wBAApD,QAAQ,GAAG,SAAyC;wBAEpD,KAAK,GAAG,CAAC,CAAC;8BACU,EAAR,qBAAQ;;;6BAAR,CAAA,sBAAQ,CAAA;wBAAf,GAAG;wBACF,qBAAM,IAAA,MAAC,EAAC,kBAAW,GAAG,CAAE,CAAC,EAAA;;wBAA7B,CAAC,GAAG,SAAyB;wBACjC,IAAI,CAAC,CAAC;4BAAE,yBAAS;wBACjB,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;wBAClB,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;4BACd,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;;4BACpD,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;;;wBANX,IAAQ,CAAA;;;wBASxB,MAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;wBACtB,KAAK,CAAC,IAAI,CAAC,MAAI,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;wBAGnB,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;;;;KACjB;IAEK,GAAG;4DAAC,EAAmB,EAAE,GAAG;;gBAAZ,GAAG,gBAAA;;;;wBACvB,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;;;;wBAEzC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;4BAC3B,IAAI,CAAC;gCACH,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;4BAClE,CAAC;4BAAC,OAAO,CAAC,EAAE,CAAC,CAAA,CAAC;wBAChB,CAAC;wBAEU,qBAAM,IAAA,eAAO,EAAC,GAAG,CAAC,EAAA;;wBAAzB,IAAI,GAAG,SAAkB;wBAE7B,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;4BAC/B,IAAI,GAAG;gCACL,QAAQ,EAAE,KAAK;gCACf,QAAQ,EAAE,GAAG;gCACb,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;gCACzB,MAAM,EAAE,GAAG;gCACX,IAAI,EAAE,IAAI;6BACX,CAAC;wBACJ,CAAC;wBAED,IAAI,CAAC,IAAI;4BAAE,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAC;wBAEnD,cAAY;4BACd,SAAS;4BACT,MAAM;4BACN,QAAQ;4BACR,QAAQ;4BACR,UAAU;4BACV,SAAS;4BACT,IAAI;4BACJ,SAAS;4BACT,YAAY;4BACZ,MAAM;4BACN,SAAS;4BACT,QAAQ;4BACR,QAAQ;4BACR,UAAU;yBACX,CAAC;wBAEF,IAAI,IAAI,CAAC,MAAM;4BACb,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;wBACvE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;wBAE5B,GAAG,CAAC,IAAI,CAAC,IAAA,YAAI,EAAC,IAAI,EAAE,WAAS,CAAC,CAAC,CAAC;;;;wBAEhC,IAAA,aAAG,EAAC,sBAAsB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;wBACvC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAEjC;IAEK,MAAM;4DAAC,EAAiB,EAAE,GAAG;;gBAApB,IAAI,UAAA,EAAE,OAAO,aAAA;;;;;wBAElB,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;wBACvC,IAAI,CAAC,IAAI,CAAC,IAAI;4BAAE,IAAA,YAAI,EAAC,yBAAyB,CAAC,CAAC;wBAC1C,IAAI,GAAK,IAAI,KAAT,CAAU;wBAChB,WAAS;4BACX,SAAS;4BACT,QAAQ;4BACR,QAAQ;4BACR,UAAU;4BACV,UAAU;4BACV,MAAM;yBACP,CAAC;wBAEK,qBAAM,IAAA,kBAAQ,EAAC,IAAA,YAAI,EAAC,IAAI,EAAE,QAAM,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAA;;wBAApD,IAAI,GAAG,SAA6C,CAAC;wBACrD,IAAA,WAAC,EAAC,qBAAqB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;wBACxC,GAAG,CAAC,IAAI,CAAC,IAAA,YAAI,EAAC,IAAI,EAAE,mBAAS,CAAC,CAAC,CAAC;;;;wBAEhC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAEjC;IAEK,UAAU;4DAAC,EAAyB,EAAE,GAAG;;gBAA5B,IAAI,UAAA,EAAU,KAAK,gBAAA;;;;wBAC9B,EAAE,GAAiC,IAAI,GAArC,EAAE,KAAK,GAA0B,IAAI,MAA9B,EAAE,QAAQ,GAAgB,IAAI,SAApB,EAAE,SAAS,GAAK,IAAI,UAAT,CAAU;wBAC9C,IAAI,KAAK,IAAI,CAAC,sBAAa,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;4BACpD,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,EAAC;wBAC5C,CAAC;wBAED,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;wBACnB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAC5B,IAAA,cAAI,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;wBAC7B,IAAA,cAAI,EAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;wBAC5C,IAAA,WAAC,EAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;wBAC5B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;KACd;IAEK,SAAS;4DAAC,EAAyB,EAAE,GAAG;;gBAA5B,IAAI,UAAA,EAAU,KAAK,gBAAA;;;;wBAC7B,EAAE,GAA0B,IAAI,GAA9B,EAAE,SAAS,GAAe,IAAI,UAAnB,EAAE,QAAQ,GAAK,IAAI,SAAT,CAAU;wBACjC,OAAO,GAAG,sBAAa,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;6BAClD,OAAO,EAAP,wBAAO;wBACT,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;wBAClB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAC5B,IAAA,cAAI,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;;4BAE7B,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,EAAC;;wBAG7C,IAAA,WAAC,EAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;wBAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;KACd;IAEK,MAAM;4DAAC,EAAc,EAAE,GAAG;;gBAAjB,IAAI,UAAA,EAAE,IAAI,UAAA;;;;;wBAErB,IAAA,WAAC,EAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;wBAGhC,OAAO,GAUL,IAAI,QAVC,EACP,MAAM,GASJ,IAAI,OATA,EACN,YAQE,IAAI,QARC,EACP,QAAQ,GAON,IAAI,SAPE,EACR,GAAG,GAMD,IAAI,IANH,EACH,MAAM,GAKJ,IAAI,OALA,EACN,MAAM,GAIJ,IAAI,OAJA,EACN,QAAQ,GAGN,IAAI,SAHE,EACR,YAAY,GAEV,IAAI,aAFM,EACZ,YAAY,GACV,IAAI,aADM,CACL;wBAET,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC;4BAAE,IAAA,YAAI,EAAC,cAAc,CAAC,CAAC;wBAC1D,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;4BACtD,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC;wBACpB,IAAI,IAAI,CAAC,GAAG,KAAK,QAAQ;4BAAE,OAAO,IAAI,CAAC,GAAG,CAAC;wBAEvC,MAAM,SAAA,CAAC;6BACP,QAAQ,EAAR,wBAAQ;wBAAW,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;wBAAhC,MAAM,GAAG,SAAuB,CAAC;;;wBAE3C,KAAK,SAAA,EAAE,OAAO,SAAA,CAAC;6BACf,CAAA,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE,IAAI,MAAM,CAAA,EAAhE,wBAAgE;wBAClE,IAAA,aAAG,EAAC,gBAAgB,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;wBAChE,IAAA,YAAI,EAAC,gBAAgB,CAAC,CAAC;;;6BACd,QAAQ,EAAR,wBAAQ;wBACjB,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE;4BACxD,IAAA,WAAC,EAAC,mBAAmB,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;wBAElD,qBAAM,OAAE,CAAC,GAAG,CAAC,eAAQ,IAAI,CAAC,QAAQ,CAAE,CAAC,EAAA;;wBAArC,SAAqC,CAAC;wBACtC,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;wBACxB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;;;6BAGvB,MAAM,EAAN,wBAAM;wBAAW,qBAAM,IAAA,eAAO,EAAC,MAAM,CAAC,EAAA;;wBAA9B,MAAM,GAAG,SAAqB,CAAC;;;6BACvC,CAAA,MAAM,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA,EAAxD,yBAAwD;wBAC1D,IAAA,cAAI,EAAC,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;6BACxC,MAAM,CAAC,IAAI,EAAX,wBAAW;wBAAE,qBAAM,OAAE,CAAC,GAAG,CAAC,eAAQ,MAAM,CAAE,CAAC,EAAA;;wBAA9B,SAA8B,CAAC;;;wBAC3C,IAAA,YAAI,EAAC,+BAA+B,CAAC,CAAC;;;wBAGzC,UAAU,GAAG;4BACf,SAAS;4BACT,cAAc;4BACd,QAAQ;4BACR,QAAQ;4BACR,YAAY;4BACZ,UAAU;4BACV,aAAa;4BACb,SAAS;4BACT,OAAO;4BACP,UAAU;4BACV,MAAM;4BACN,UAAU;4BACV,YAAY;4BACZ,MAAM;4BACN,QAAQ;4BACR,MAAM;4BACN,QAAQ;4BACR,SAAS;4BACT,QAAQ;4BACR,SAAS;4BACT,MAAM;4BACN,MAAM;4BACN,WAAW;4BACX,QAAQ;4BACR,OAAO;4BACP,cAAc;4BACd,cAAc;yBACf,CAAC;wBAEF,WAAwB,EAAV,yBAAU,EAAV,wBAAU,EAAV,IAAU,EAAE,CAAC;4BAAlB,CAAC;4BACR,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,WAAW;gCAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;wBACxD,CAAC;6BAEG,CAAA,QAAQ,IAAI,QAAQ,KAAK,SAAO,CAAA,EAAhC,yBAAgC;wBAClC,KAAA,IAAI,CAAA;wBAAY,qBAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE;gCAChD,SAAS,EAAE,QAAQ;gCACnB,IAAI,EAAE,CAAC;6BACR,CAAC,EAAA;;wBAHF,GAAK,QAAQ,GAAG,SAGd,CAAC;;;wBAGL,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;wBACzB,qBAAM,IAAA,MAAC,EAAC,eAAQ,IAAI,CAAC,MAAM,CAAE,EAAE,IAAI,CAAC,EAAE,CAAC,EAAA;;wBAAvC,SAAuC,CAAC;wBACxC,qBAAM,IAAA,MAAC,EACL,eAAQ,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAE,EACxD,IAAI,CAAC,EAAE,CACR,EAAA;;wBAHD,SAGC,CAAC;wBAEF,qBAAM,IAAA,MAAC,EAAC,eAAQ,IAAI,CAAC,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAAhC,SAAgC,CAAC;6BAC7B,IAAI,CAAC,IAAI,EAAT,yBAAS;wBAAE,qBAAM,OAAE,CAAC,IAAI,CAAC,MAAM,EAAE,UAAG,IAAI,CAAC,QAAQ,cAAI,IAAI,CAAC,MAAM,CAAE,CAAC,EAAA;;wBAAxD,SAAwD,CAAC;;;wBAExE,IAAA,cAAI,EAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;wBAC5B,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,MAAA,EAAE,KAAK,OAAA,EAAE,CAAC,CAAC;;;;wBAE1B,IAAA,cAAI,EAAC,kBAAkB,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;wBACnD,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,KAAK,YAAC,GAAG,EAAE,GAAG;;;;;;;wBAEZ,KAAuC,GAAG,CAAC,IAAI,EAA7C,QAAQ,cAAA,EAAE,QAAQ,cAAA,EAAS,KAAK,WAAA,CAAc;wBAEpD,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,MAAM;4BAC9C,IAAA,WAAC,EAAC,YAAY,EAAE,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC;wBAE7D,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;wBAC1C,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;wBAA9B,IAAI,GAAG,SAAuB;wBAGhC,KAAA,CAAC,IAAI,CAAA;gCAAL,wBAAK;wBACJ,KAAA,IAAI,CAAC,QAAQ,CAAA;iCAAb,wBAAa;wBAEV,KAAA,CAAC,iBAAM,CAAC,SAAS,IAAI,QAAQ,KAAK,iBAAM,CAAC,SAAS,CAAC,CAAA;gCAAnD,wBAAmD;wBAClD,qBAAM,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAA;;wBAAnD,KAAA,CAAC,SAAkD,CAAC,CAAA;;;wBAFtD,KAAA,CAAC,IAGA,CAAA;;;wBAJH,KAAA,IAII,CAAA;;;wBANN,QAOE,CAAC;4BACD,IAAA,cAAI,EAAC,sCAAsC,EAAE,QAAQ,CAAC,CAAC;4BACvD,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAC;wBAChC,CAAC;wBAED,IACE,CAAC,CAAC,iBAAM,CAAC,SAAS,IAAI,QAAQ,KAAK,iBAAM,CAAC,SAAS,CAAC;4BACpD,IAAI,CAAC,KAAK;4BACV,CAAC,OAAO,KAAK,KAAK,WAAW;gCAC3B,CAAC,sBAAa,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAC9C,CAAC;4BACD,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,EAAC;wBAC5C,CAAC;wBAED,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,MAAM;4BAC9C,IAAA,WAAC,EAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;wBAEvB,OAAO,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;wBAC1B,KAAK,GAAG,sBAAG,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAM,CAAC,GAAG,CAAC,CAAC;wBAC1C,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;wBAC1E,IAAI,GAAG,IAAA,YAAI,EAAC,IAAI,EAAE,mBAAS,CAAC,CAAC;wBAC7B,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,MAAA,EAAE,KAAK,OAAA,EAAE,CAAC,CAAC;;;;wBAE1B,IAAA,aAAG,EAAC,aAAa,EAAE,GAAC,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;wBACxD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;;KAE1B;IAEK,SAAS;4DAAC,EAAc,EAAE,GAAG;;gBAAjB,IAAI,UAAA,EAAE,IAAI,UAAA;;;;wBACpB,aAAa,GAAK,IAAI,cAAT,CAAU;wBACvB,YAAY,GAAK,IAAI,aAAT,CAAU;wBAC5B,IAAI,CAAC,aAAa;4BAAE,aAAa,GAAG,EAAE,CAAC;wBACvC,IACE,CAAC,aAAa,CAAC,IAAI,CACjB,UAAC,CAAC,IAAK,OAAA,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,EAAlD,CAAkD,CAC1D;4BAED,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;wBACnC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;wBACnC,IAAA,WAAC,EAAC,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;wBAChC,qBAAM,IAAI,CAAC,IAAI,EAAE,EAAA;;wBAAjB,SAAiB,CAAC;wBAClB,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;;;;;KACrB;IAEK,QAAQ;4DAAC,EAA4B,EAAE,GAAG;;gBAAvB,QAAQ,mBAAA,EAAI,IAAI,UAAA;;;;wBACvC,IAAI,CAAC,IAAI,CAAC,QAAQ;4BAAE,sBAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAC;;;;wBAGxC,IAAI,CAAC,QAAQ;4BAAE,IAAA,YAAI,EAAC,uBAAuB,CAAC,CAAC;wBAC7C,KAAA,CAAA,KAAA,GAAG,CAAA,CAAC,IAAI,CAAA;wBAAC,qBAAM,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAA;;wBAA3D,cAAS,SAAkD,EAAC,CAAC;;;;wBAE7D,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,GAAG;4DAAC,EAAuB,EAAE,GAAG;gBAAlB,GAAG,cAAA,EAAI,IAAI,UAAA;;gBAC7B,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;;;;KACzC;IAEK,SAAS,YAAC,GAAG,EAAE,GAAG;;;;;;;wBAEpB,qBAAM,IAAA,iBAAU,EAAC,GAAG,CAAC,EAAA;;wBAArB,SAAqB,CAAC;wBAClB,KAA0B,GAAG,CAAC,IAAI,EAAhC,SAAS,eAAA,EAAE,QAAQ,cAAA,CAAc;wBACvC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,UAAA,EAAE,CAAC,CAAC;;;;wBAE1C,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAEjC;IAEK,QAAQ;4DAAC,EAAgB,EAAE,GAAG;;;gBAAX,EAAE,aAAA;;;4BACV,qBAAM,IAAA,MAAC,EAAC,UAAG,EAAE,aAAU,CAAC,EAAA;;wBAAnC,OAAO,GAAG,CAAC,SAAwB,CAAC,IAAI,CAAC;wBACnC,qBAAM,OAAE,CAAC,IAAI,CAAC,UAAG,EAAE,cAAW,CAAC,EAAA;;wBAArC,GAAG,GAAG,SAA+B;wBACzB,qBAAM,OAAE,CAAC,MAAM,CAAC,UAAG,EAAE,cAAW,EAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,EAAA;;wBAA/D,QAAQ,GAAG,CAAC,SAAmD,CAAC,IAAI,EAAE;wBAC1E,qBAAM,OAAE,CAAC,GAAG,CAAC,UAAG,EAAE,aAAU,EAAE,GAAG,CAAC,EAAA;;wBAAlC,SAAkC,CAAC;wBAEnB,qBAAM,IAAA,MAAC,EAAC,UAAG,EAAE,cAAW,CAAC,EAAA;;wBAArC,QAAQ,GAAG,CAAC,SAAyB,CAAC,IAAI,EAAE;4CAErC,GAAG;;;;;wCAKR,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,GAAG,EAAjB,CAAiB,CAAC,CAAC;wCACrD,IAAI,CAAC,CAAC;4CAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;wCACtB,qBAAM,IAAA,MAAC,EAAC,eAAQ,GAAG,CAAE,CAAC,EAAA;;wCAA1B,CAAC,GAAG,SAAsB;6CAC1B,CAAA,OAAO,CAAC,KAAK,QAAQ,CAAA,EAArB,wBAAqB;wCAAM,qBAAM,IAAA,MAAC,EAAC,eAAQ,GAAG,CAAE,CAAC,EAAA;;wCAA1B,CAAC,GAAG,SAAsB,CAAC;;;wCACtD,IAAI,CAAC;4CAAE,QAAQ,CAAC,OAAO,CAAC,IAAA,YAAI,EAAC,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;;;;;8BALV;wBAHtD,qBAAM,OAAO,CAAC,GAAG,CACf,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,UAAO,EAAE;;4CAAK,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;4CAAxB,sBAAA,SAAwB,EAAA;;qCAAA,CAAC,CAC/D,EAAA;;wBAHiB,KAAA,CAClB,SAEC,CACF,CAAC,MAAM,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,gBAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,EAAvC,CAAuC,CAAC;;;6BAJpC,CAAA,cAIoC,CAAA;wBAJ7C,GAAG,aAAA;sDAAH,GAAG;;;;;wBAAM,IAIoC,CAAA;;6BAQxD,qBAAM,IAAA,MAAC,EAAC,UAAG,EAAE,cAAW,EAAE,QAAQ,CAAC,EAAA;;wBAAnC,SAAmC,CAAC;wBAEpC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;;;;;KACpB;IAEK,GAAG;4DAAC,EAAoD,EAAE,GAAG;;gBAA7C,QAAQ,qBAAA,EAAe,aAAa,2BAAA;;;;wBACxD,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;wBAClC,IAAI,CAAC,CAAC,aAAa,IAAI,aAAa,CAAC,QAAQ,CAAC,iBAAM,CAAC,KAAK,CAAC,CAAC;4BAC1D,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,EAAC;wBAEjB,KAAA,MAAC,CAAA;;wBAClB,qBAAM,IAAA,MAAC,EAAC,eAAQ,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAE,CAAC,EAAA;4BADjD,qBAAM,kBACzB,mBAAQ,SAA4D,EAAE,EACvE,EAAA;;wBAFG,KAAiB,SAEpB,EAFK,EAAE,QAAA,EAAE,MAAM,YAAA;wBAGD,qBAAM,OAAE,CAAC,MAAM,CAAC,UAAG,EAAE,cAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAA;;wBAAnD,QAAQ,GAAG,SAAwC;wBACxC,qBAAM,OAAE,CAAC,MAAM,CAAC,UAAG,EAAE,cAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAA;;wBAAnD,QAAQ,GAAG,SAAwC;wBAEvD,WAA2B,EAAR,qBAAQ,EAAR,sBAAQ,EAAR,IAAQ;4BAAhB,wBAAE;4BAAgB,OAAE,CAAC,GAAG,CAAC,kBAAW,IAAE,CAAE,CAAC,CAAC;yBAAA;wBACrD,WAA2B,EAAR,qBAAQ,EAAR,sBAAQ,EAAR,IAAQ;4BAAhB,wBAAE;4BAAgB,OAAE,CAAC,GAAG,CAAC,kBAAW,IAAE,CAAE,CAAC,CAAC;yBAAA;wBACrD,OAAE,CAAC,GAAG,CAAC,eAAQ,QAAQ,CAAC,WAAW,EAAE,CAAE,CAAC,CAAC;wBACzC,OAAE,CAAC,GAAG,CAAC,eAAQ,EAAE,CAAE,CAAC,CAAC;wBACrB,OAAE,CAAC,GAAG,CAAC,eAAQ,MAAM,CAAE,CAAC,CAAC;wBAEzB,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;KACd;IAEK,KAAK;4DAAC,EAA+C,EAAE,GAAG;;gBAAlD,YAAkC,EAA1B,IAAI,UAAA,EAAE,QAAQ,cAAA,EAAE,QAAQ,cAAA,EAAU,CAAC,UAAA;;;;wBACnD,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;6BAGrB,KAAK,EAAL,wBAAK;wBACF,qBAAM,IAAA,MAAC,EAAC,eAAQ,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAE,CAAC,EAAA;;wBAAjE,EAAE,GAAG,SAA4D,CAAC;wBAC3D,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;wBAA5B,IAAI,GAAG,SAAqB,CAAC;;4BAExB,qBAAM,IAAA,MAAC,EAAC,gBAAS,IAAI,CAAE,CAAC,EAAA;;wBAA7B,EAAE,GAAG,SAAwB,CAAC;wBACvB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;wBAA5B,IAAI,GAAG,SAAqB,CAAC;;;wBAG/B,IAAI,CAAC,IAAI;4BAAE,IAAA,YAAI,EAAC,gBAAgB,CAAC,CAAC;;;;wBAGhC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;wBAChB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;wBACnB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;wBACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;wBACjB,KAAA,IAAI,CAAA;wBAAY,qBAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE;gCAChD,SAAS,EAAE,QAAQ;gCACnB,IAAI,EAAE,CAAC;6BACR,CAAC,EAAA;;wBAHF,GAAK,QAAQ,GAAG,SAGd,CAAC;wBAEH,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAC5B,qBAAM,OAAE,CAAC,GAAG,CAAC,gBAAS,IAAI,CAAE,CAAC,EAAA;;wBAA7B,SAA6B,CAAC;wBAE9B,GAAG,CAAC,IAAI,CAAC,IAAA,YAAI,EAAC,IAAI,EAAE,mBAAS,CAAC,CAAC,CAAC;;;;wBAEhC,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,YAAY;4DAAC,EAA6B,EAAE,GAAG;gBAAhC,YAAyB,EAAjB,QAAQ,cAAA,EAAE,KAAK,WAAA;;gBAC1C,IAAI,QAAQ,KAAK,KAAK;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;oBAC1C,IAAA,YAAI,EAAC,GAAG,EAAE,cAAc,CAAC,CAAC;;;;KAChC;IAEK,GAAG;4DAAC,EAA6B,EAAE,GAAG;gBAAhC,YAAyB,EAAjB,QAAQ,cAAA,EAAE,KAAK,WAAA;;gBACjC,IAAI,QAAQ,KAAK,KAAK;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;oBAC1C,IAAA,YAAI,EAAC,GAAG,EAAE,cAAc,CAAC,CAAC;;;;KAChC;IAEK,SAAS;4DAAC,EAAsB,EAAE,GAAG;gBAAjB,QAAQ,mBAAA;;gBAChC,IAAI,QAAQ,KAAK,iBAAM,CAAC,KAAK,CAAC,QAAQ;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;oBAC1D,IAAA,YAAI,EAAC,GAAG,EAAE,cAAc,CAAC,CAAC;;;;KAChC;IAEK,OAAO;4DAAC,EAAuB,EAAE,GAAG;;gBAA1B,YAAmB,EAAX,EAAE,QAAA,EAAE,KAAK,WAAA;;;;;wBAElB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;wBAA5B,IAAI,GAAG,SAAqB;wBAEhC,IAAI,CAAC,IAAI;4BAAE,IAAA,YAAI,EAAC,gBAAgB,CAAC,CAAC;wBAC9B,qBAAM,IAAA,MAAC,EAAC,gBAAS,KAAK,CAAC,WAAW,EAAE,CAAE,CAAC,EAAA;;wBAA3C,IAAI,SAAuC;4BAAE,IAAA,YAAI,EAAC,sBAAsB,CAAC,CAAC;wBAEpE,QAAQ,GAAK,IAAI,SAAT,CAAU;6BAEpB,CAAA,KAAK,KAAK,IAAI,CAAC,KAAK,CAAA,EAApB,wBAAoB;wBACtB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;wBACtB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAC5B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;;;wBAGjB,IAAI,GAAG,IAAA,SAAE,GAAE,CAAC;wBAChB,qBAAM,IAAA,MAAC,EAAC,iBAAU,IAAI,CAAE,EAAE,EAAE,EAAE,IAAA,EAAE,KAAK,OAAA,EAAE,CAAC,EAAA;;wBAAxC,SAAwC,CAAC;wBACrC,IAAI,GAAG,UAAG,OAAO,CAAC,GAAG,CAAC,GAAG,cAAI,QAAQ,qBAAW,IAAI,CAAE,CAAC;wBACvD,OAAO,GAAG,oBAAoB,CAAC;wBAEnC,qBAAM,IAAA,WAAI,EAAC,IAAI,EAAE,OAAO,EAAE,gBAAS,CAAC,WAAW,EAAE;gCAC/C,QAAQ,UAAA;gCACR,IAAI,MAAA;6BACL,CAAC,EAAA;;wBAHF,SAGE,CAAC;wBAEH,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;;;wBAEvB,IAAA,YAAI,EAAC,GAAG,EAAE,IAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,MAAM;4DAAC,EAAoB,EAAE,GAAG;;gBAAb,IAAI,iBAAA;;;;;wBAEL,qBAAM,IAAA,MAAC,EAAC,iBAAU,IAAI,CAAE,CAAC,EAAA;;wBAAzC,KAAgB,SAAyB,EAAvC,EAAE,QAAA,EAAE,KAAK,WAAA;wBACf,IAAI,CAAC,EAAE;4BAAE,IAAA,YAAI,EAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;wBAC/B,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;wBAA5B,IAAI,GAAG,SAAqB;wBAChC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;wBACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;wBACrB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAC5B,qBAAM,IAAA,MAAC,EAAC,gBAAS,KAAK,CAAC,WAAW,EAAE,CAAE,EAAE,EAAE,CAAC,EAAA;;wBAA3C,SAA2C,CAAC;wBAE5C,GAAG,CAAC,IAAI,CAAC,IAAA,YAAI,EAAC,IAAI,EAAE,mBAAS,CAAC,CAAC,CAAC;;;;wBAEhC,IAAA,YAAI,EAAC,GAAG,EAAE,IAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,MAAM;4DAAC,EAAmB,EAAE,GAAG;;gBAAd,KAAK,gBAAA;;;;;wBAEd,qBAAM,IAAA,MAAC,EAAC,gBAAS,KAAK,CAAC,WAAW,EAAE,CAAE,CAAC,EAAA;;wBAA7C,GAAG,GAAG,SAAuC;wBACtC,qBAAM,IAAA,MAAC,EAAC,eAAQ,GAAG,CAAE,CAAC,EAAA;;wBAA7B,IAAI,GAAG,SAAsB;6BAE7B,IAAI,EAAJ,wBAAI;wBACF,IAAI,GAAG,IAAA,SAAE,GAAE,CAAC;wBACZ,IAAI,GAAG,UAAG,OAAO,CAAC,GAAG,CAAC,GAAG,oBAAU,IAAI,CAAE,CAAC;wBAC9C,qBAAM,IAAA,MAAC,EAAC,gBAAS,IAAI,CAAE,EAAE,GAAG,CAAC,EAAA;;wBAA7B,SAA6B,CAAC;wBAE9B,qBAAM,IAAA,WAAI,EAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAS,CAAC,aAAa,wBACrD,IAAI,KACP,IAAI,MAAA,IACJ,EAAA;;wBAHF,SAGE,CAAC;;;wBAGL,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;wBAEb,IAAA,YAAI,EAAC,GAAG,EAAE,IAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,KAAK;4DAAC,EAAkB,EAAE,GAAG;;gBAAX,EAAE,eAAA;;;4BACb,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;wBAA5B,IAAI,GAAG,SAAqB;wBAChC,sBAAO,IAAI,EAAC;;;;KACb;IAEK,OAAO;4DAAC,EAAsB,EAAE,GAAG;;gBAAjB,QAAQ,mBAAA;;;4BACtB,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;wBAA3B,CAAC,GAAG,SAAuB;wBAC/B,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC;wBACjB,qBAAM,IAAA,MAAC,EAAC,eAAQ,CAAC,CAAC,EAAE,CAAE,EAAE,CAAC,CAAC,EAAA;;wBAA1B,SAA0B,CAAC;wBAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;KACd;IAEK,OAAO;4DAAC,EAAsB,EAAE,GAAG;;gBAAjB,QAAQ,mBAAA;;;4BACtB,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;wBAA3B,CAAC,GAAG,SAAuB;wBAC/B,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC;wBACnB,qBAAM,IAAA,MAAC,EAAC,eAAQ,CAAC,CAAC,EAAE,CAAE,EAAE,CAAC,CAAC,EAAA;;wBAA1B,SAA0B,CAAC;wBAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;KACd;CACF,CAAC"} \ No newline at end of file diff --git a/routes/users.ts b/routes/users.ts new file mode 100644 index 00000000..17bea006 --- /dev/null +++ b/routes/users.ts @@ -0,0 +1,556 @@ +import { g, s, db } from "$lib/db"; +import config from "$config"; +import store from "$lib/store"; +import { + fields, + nada, + pick, + uniq, + wait, + bail, + fail, + getUser, +} from "$lib/utils"; +import jwt from "jsonwebtoken"; +import { authenticator } from "otplib"; +import whitelist from "$lib/whitelist"; +import { l, err, warn } from "$lib/logging"; +import { emit } from "$lib/sockets"; +import register from "$lib/register"; +import { requirePin } from "$lib/auth"; +import { v4 } from "uuid"; +import { parseISO } from "date-fns"; +import { types } from "$lib/payments"; +import { bech32 } from "bech32"; +import { mail, templates } from "$lib/mail"; + +import got from "got"; +import upload from "$lib/upload"; + +const { encode, decode, fromWords, toWords } = bech32; + +export default { + upload, + + async me({ user }, res) { + try { + user.balance = await g(`balance:${user.id}`); + user.prompt = !!user.prompt; + if (user.pubkey) + user.npub = encode("npub", toWords(Buffer.from(user.pubkey, "hex"))); + + res.send(pick(user, whitelist)); + } catch (e) { + console.log("problem fetching user", e); + res.code(500).send(e.message); + } + }, + + async list({ user }, res) { + if (!user.admin) fail("unauthorized"); + + let users = []; + + for await (let k of db.scanIterator({ MATCH: "balance:*" })) { + let uid = k.split(":")[1]; + let user = await getUser(uid); + + if (!user) { + await db.del(`balance:${uid}`); + continue; + } + + user.balance = await g(k); + + let payments = await db.lRange(`${uid}:payments`, 0, -1); + + let total = 0; + for (let pid of payments) { + let p = await g(`payment:${pid}`); + if (!p) continue; + total += p.amount; + if (p.amount < 0) + total -= (p.fee || 0) + (p.ourfee || 0) + (p.tip || 0); + else total += p.tip || 0; + } + + user.expected = total; + users.push(user); + } + + res.send(users); + }, + + async get({ params: { key } }, res) { + key = key.toLowerCase().replace(/\s/g, ""); + try { + if (key.startsWith("npub")) { + try { + key = Buffer.from(fromWords(decode(key).words)).toString("hex"); + } catch (e) {} + } + + let user = await getUser(key); + + if (!user && key.length === 64) { + user = { + currency: "USD", + username: key, + display: key.substr(0, 6), + pubkey: key, + anon: true, + }; + } + + if (!user) return res.code(500).send("User not found"); + + let whitelist = [ + "address", + "anon", + "banner", + "banner", + "currency", + "display", + "id", + "hidepay", + "memoPrompt", + "npub", + "profile", + "prompt", + "pubkey", + "username", + ]; + + if (user.pubkey) + user.npub = encode("npub", toWords(Buffer.from(user.pubkey, "hex"))); + user.prompt = !!user.prompt; + + res.send(pick(user, whitelist)); + } catch (e) { + err("problem getting user", e.message); + res.code(500).send(e.message); + } + }, + + async create({ body, headers }, res) { + try { + const ip = headers["cf-connecting-ip"]; + if (!body.user) fail("no user object provided"); + let { user } = body; + let fields = [ + "profile", + "cipher", + "pubkey", + "password", + "username", + "salt", + ]; + + user = await register(pick(user, fields), ip, false); + l("registered new user", user.username); + res.send(pick(user, whitelist)); + } catch (e) { + res.code(500).send(e.message); + } + }, + + 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"); + } + + user.twofa = false; + await s(`user:${id}`, user); + emit(username, "user", user); + emit(username, "otpsecret", user.otpsecret); + l("disabled 2fa", username); + res.send({}); + }, + + 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"); + } + + l("enabled 2fa", username); + res.send({}); + }, + + async update({ user, body }, res) { + try { + l("updating user", user.username); + + let { + profile, + banner, + confirm, + password, + pin, + newpin, + pubkey, + username, + shopifyToken, + shopifyStore, + } = body; + + if (user.pin && !(pin === user.pin)) fail("Pin required"); + if (typeof newpin !== "undefined" && newpin.length === 6) + user.pin = newpin; + if (user.pin === "delete") delete user.pin; + + let exists; + if (username) exists = await getUser(username); + + let token, oldname; + if (user.username.toLowerCase() !== username.toLowerCase() && exists) { + err("username taken", username, user.username, exists.username); + fail("Username taken"); + } else if (username) { + if (user.username.toLowerCase() !== username.toLowerCase()) + l("changing username", user.username, username); + + await db.del(`user:${user.username}`); + oldname = user.username; + user.username = username; + } + + if (pubkey) exists = await getUser(pubkey); + if (exists && ![oldname, username].includes(exists.username)) { + warn("key in use", pubkey, exists.username); + if (exists.anon) await db.del(`user:${pubkey}`); + else fail("Key in use by another account"); + } + + let attributes = [ + "address", + "autowithdraw", + "banner", + "cipher", + "currencies", + "currency", + "destination", + "display", + "email", + "language", + "fiat", + "locktime", + "memoPrompt", + "nip5", + "notify", + "nsec", + "prompt", + "profile", + "pubkey", + "reserve", + "salt", + "seed", + "threshold", + "tokens", + "twofa", + "shopifyToken", + "shopifyStore", + ]; + + for (let a of attributes) { + if (typeof body[a] !== "undefined") user[a] = body[a]; + } + + if (password && password === confirm) { + user.password = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 1, + }); + } + + user.haspin = !!user.pin; + await s(`user:${user.pubkey}`, user.id); + await s( + `user:${user.username.toLowerCase().replace(/\s/g, "")}`, + user.id, + ); + + await s(`user:${user.id}`, user); + if (user.nip5) await db.sAdd("nip5", `${user.username}:${user.pubkey}`); + + emit(user.id, "user", user); + res.send({ user, token }); + } catch (e) { + warn("failed to update", user.username, e.message); + bail(res, e.message); + } + }, + + async login(req, res) { + try { + let { username, password, token: twofa } = req.body; + + if (username !== "coinos" && username !== "funk") + l("logging in", username, req.headers["cf-connecting-ip"]); + + username = username.toLowerCase().replace(/\s/g, ""); + let user = await getUser(username); + + if ( + !user || + (user.password && + !( + (config.adminpass && password === config.adminpass) || + (await Bun.password.verify(password, user.password)) + )) + ) { + warn("invalid username or password attempt", username); + return res.code(401).send({}); + } + + if ( + !(config.adminpass && password === config.adminpass) && + user.twofa && + (typeof twofa === "undefined" || + !authenticator.check(twofa, user.otpsecret)) + ) { + return res.code(401).send("2fa required"); + } + + if (username !== "coinos" && username !== "funk") + l("logged in", username); + + let payload = { 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) { + err("login error", e.message, req.socket.remoteAddress); + res.code(401).send({}); + } + }, + + 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); + + try { + if (!password) fail("password not provided"); + res.send(await Bun.password.verify(password, user.password)); + } catch (e) { + bail(res, e.message); + } + }, + + async pin({ body: { pin }, user }, res) { + res.send(!user.pin || user.pin === pin); + }, + + 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); + } + }, + + async contacts({ user: { id } }, res) { + let lastlen = (await g(`${id}:lastlen`)) || 0; + let len = await db.lLen(`${id}:payments`); + let payments = (await db.lRange(`${id}:payments`, 0, len - lastlen)) || []; + await db.set(`${id}:lastlen`, len); + + let contacts = (await g(`${id}:contacts`)) || []; + + for (let { ref } of ( + await Promise.all( + payments.reverse().map(async (id) => await g(`payment:${id}`)), + ) + ).filter((p) => p && p.type === types.internal && p.ref)) { + let i = contacts.findIndex((c) => c && c.id === ref); + if (~i) contacts.splice(i, 1); + let u = await g(`user:${ref}`); + if (typeof u === "string") u = await g(`user:${ref}`); + if (u) contacts.unshift(pick(u, ["id", "profile", "username"])); + } + + await s(`${id}:contacts`, contacts); + + res.send(contacts); + }, + + async del({ params: { username }, headers: { authorization } }, res) { + username = username.toLowerCase(); + if (!(authorization && authorization.includes(config.admin))) + return res.code(401).send("unauthorized"); + + let { id, pubkey } = await g( + `user:${await g(`user:${username.replace(/\s/g, "").toLowerCase()}`)}`, + ); + let invoices = await db.lRange(`${id}:invoices`, 0, -1); + let payments = await db.lRange(`${id}:payments`, 0, -1); + + for (let { id } of invoices) db.del(`invoice:${id}`); + for (let { id } of payments) db.del(`payment:${id}`); + db.del(`user:${username.toLowerCase()}`); + db.del(`user:${id}`); + db.del(`user:${pubkey}`); + + res.send({}); + }, + + async reset({ body: { code, username, password }, user: u }, res) { + let admin = u && u.admin; + let id, user; + + if (admin) { + id = await g(`user:${username.toLowerCase().replace(/\s/g, "")}`); + user = await g(`user:${id}`); + } else { + id = await g(`reset:${code}`); + user = await g(`user:${id}`); + } + + if (!user) fail("user not found"); + + try { + user.pin = null; + user.pubkey = null; + user.cipher = null; + user.salt = null; + user.password = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 1, + }); + + await s(`user:${id}`, user); + await db.del(`reset:${code}`); + + res.send(pick(user, whitelist)); + } catch (e) { + bail(res, e.message); + } + }, + + async printerlogin({ body: { username, topic } }, res) { + if (username === topic) res.send({ ok: true }); + else bail(res, "unauthorized"); + }, + + async acl({ body: { username, topic } }, res) { + if (username === topic) res.send({ ok: true }); + else bail(res, "unauthorized"); + }, + + async superuser({ body: { username } }, res) { + if (username === config.mqtt2.username) res.send({ ok: true }); + else bail(res, "unauthorized"); + }, + + async request({ body: { id, email } }, res) { + try { + let user = await g(`user:${id}`); + + if (!user) fail("user not found"); + if (await g(`email:${email.toLowerCase()}`)) fail("Email already in use"); + + let { username } = user; + + if (email !== user.email) { + user.verified = false; + await s(`user:${id}`, user); + user.email = email; + } + + let code = v4(); + await s(`verify:${code}`, { id, email }); + let link = `${process.env.URL}/${username}/verify/${code}`; + let subject = "Email Verification"; + + await mail(user, subject, templates.verifyEmail, { + username, + link, + }); + + res.send({ ok: true }); + } catch (e) { + bail(res, e.message); + } + }, + + async verify({ params: { code } }, res) { + try { + let { id, email } = await g(`verify:${code}`); + if (!id) fail(res, "verification failed"); + let user = await g(`user:${id}`); + user.email = email; + user.verified = true; + await s(`user:${id}`, user); + await s(`email:${email.toLowerCase()}`, id); + + res.send(pick(user, whitelist)); + } catch (e) { + bail(res, e.message); + } + }, + + async forgot({ body: { email } }, res) { + try { + let uid = await g(`email:${email.toLowerCase()}`); + let user = await g(`user:${uid}`); + + if (user) { + let code = v4(); + let link = `${process.env.URL}/reset/${code}`; + await s(`reset:${code}`, uid); + + await mail(user, "Password reset", templates.passwordReset, { + ...user, + link, + }); + } + + res.send({}); + } catch (e) { + bail(res, e.message); + } + }, + + async items({ params: { id } }, res) { + let item = await g(`item:${id}`); + return item; + }, + + async hidepay({ body: { username } }, res) { + let u = await getUser(username); + u.hidepay = true; + await s(`user:${u.id}`, u); + res.send({}); + }, + + async unlimit({ body: { username } }, res) { + let u = await getUser(username); + u.unlimited = true; + await s(`user:${u.id}`, u); + res.send({}); + }, +}; diff --git a/sampledata/index.js b/sampledata/index.js index a2f2e259..a39da7ea 100644 --- a/sampledata/index.js +++ b/sampledata/index.js @@ -1,16 +1,19 @@ -export default { - port: 3119, - nostr: "ws://nostr:8080", - ipxapi: "4275|PqKjKaxIlzooToria2m4XFLzvHihSpsZMX4hvgxu", - postmark: "f64dffd2-84ea-47c7-ba9f-95e053a2d0ae", - jwt: "00d0ab4f7738a83feb37f661526512063c41e49278b7c32cba87314269a5788b", - bitcoin: { - host: "bitcoin", - wallet: "coinos", - username: "admin1", - password: "123", - network: "regtest", - port: 18443, - }, - lightning: "/app/config/lightning/regtest/lightning-rpc", +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = { + port: 3119, + nostr: "ws://nostr:8080", + ipxapi: "4275|PqKjKaxIlzooToria2m4XFLzvHihSpsZMX4hvgxu", + postmark: "f64dffd2-84ea-47c7-ba9f-95e053a2d0ae", + jwt: "00d0ab4f7738a83feb37f661526512063c41e49278b7c32cba87314269a5788b", + bitcoin: { + host: "bitcoin", + wallet: "coinos", + username: "admin1", + password: "123", + network: "regtest", + port: 18443, + }, + lightning: "/app/config/lightning/regtest/lightning-rpc", }; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/sampledata/index.js.map b/sampledata/index.js.map new file mode 100644 index 00000000..1bbf56d5 --- /dev/null +++ b/sampledata/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,iBAAiB;IACxB,MAAM,EAAE,+CAA+C;IACvD,QAAQ,EAAE,sCAAsC;IAChD,GAAG,EAAE,kEAAkE;IACvE,OAAO,EAAE;QACP,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE,QAAQ;QAClB,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,SAAS;QAClB,IAAI,EAAE,KAAK;KACZ;IACD,SAAS,EAAE,6CAA6C;CACzD,CAAC"} \ No newline at end of file diff --git a/sampledata/index.ts b/sampledata/index.ts new file mode 100644 index 00000000..a2f2e259 --- /dev/null +++ b/sampledata/index.ts @@ -0,0 +1,16 @@ +export default { + port: 3119, + nostr: "ws://nostr:8080", + ipxapi: "4275|PqKjKaxIlzooToria2m4XFLzvHihSpsZMX4hvgxu", + postmark: "f64dffd2-84ea-47c7-ba9f-95e053a2d0ae", + jwt: "00d0ab4f7738a83feb37f661526512063c41e49278b7c32cba87314269a5788b", + bitcoin: { + host: "bitcoin", + wallet: "coinos", + username: "admin1", + password: "123", + network: "regtest", + port: 18443, + }, + lightning: "/app/config/lightning/regtest/lightning-rpc", +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..1a5456af --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + "moduleResolution": "nodenext", + "allowImportingTsExtensions": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + + // Some stricter flags + "noUnusedLocals": true, + "noUnusedParameters": true, + "noPropertyAccessFromIndexSignature": true, + + "baseUrl": ".", + "paths": { + "$lib/*": ["./lib/*.ts"], + "$routes/*": ["./routes/*.ts"], + "$config": ["./config.ts"] + } + } +} -- cgit v1.2.3