diff --git a/apps/boltcard/TODO.txt b/apps/boltcard/TODO.txt new file mode 100644 index 00000000000..94e04f7671c --- /dev/null +++ b/apps/boltcard/TODO.txt @@ -0,0 +1,4 @@ +- rate limits +- proper link of .gql (instead of copy) +- Tilt setup +- next-apisation? \ No newline at end of file diff --git a/apps/boltcard/bats/e2e-test.bats b/apps/boltcard/bats/e2e-test.bats index 9b30e642501..7fd4f86226d 100644 --- a/apps/boltcard/bats/e2e-test.bats +++ b/apps/boltcard/bats/e2e-test.bats @@ -45,4 +45,10 @@ load "../../../test/bats/helpers/ln" result=$(curl -s "${CALLBACK_URL}?k1=${K1_VALUE}&pr=${payment_request}") [[ result.status == "OK" ]] || exit 1 +} + +@test "wipecard" { + "skip" + id="" + curl -s http://localhost:3000/wipeboltcard } \ No newline at end of file diff --git a/apps/boltcard/callback.ts b/apps/boltcard/callback.ts index 5c157239dfc..a4f31932394 100644 --- a/apps/boltcard/callback.ts +++ b/apps/boltcard/callback.ts @@ -98,7 +98,15 @@ boltcardRouter.get("/callback", async (req: express.Request, res: express.Respon }, }) - const data = await graphQLClient.request(getUsdWalletIdQuery) + let data: GetUsdWalletIdQuery + try { + data = await graphQLClient.request(getUsdWalletIdQuery) + } catch (error) { + console.error(error) + res.status(400).send({ status: "ERROR", reason: "issue fetching walletId" }) + return + } + const wallets = data.me?.defaultAccount.wallets if (!wallets) { @@ -116,10 +124,17 @@ boltcardRouter.get("/callback", async (req: express.Request, res: express.Respon return } - const result = await graphQLClient.request({ - document: lnInvoicePaymentSendMutation, - variables: { input: { walletId: usdWalletId, paymentRequest: pr } }, - }) + let result: LnInvoicePaymentSendMutation + try { + result = await graphQLClient.request({ + document: lnInvoicePaymentSendMutation, + variables: { input: { walletId: usdWalletId, paymentRequest: pr } }, + }) + } catch (error) { + console.error(error) + res.status(400).send({ status: "ERROR", reason: "payment failed" }) + return + } if (result.lnInvoicePaymentSend.status === "SUCCESS") { res.json({ status: "OK" }) diff --git a/apps/boltcard/config.ts b/apps/boltcard/config.ts index 59bc0e4620a..a29aa8ce171 100644 --- a/apps/boltcard/config.ts +++ b/apps/boltcard/config.ts @@ -2,6 +2,7 @@ export const serverUrl = process.env.SERVER_URL ?? "http://localhost:3000" export const apiUrl = process.env.API_URL ?? "http://localhost:4002/graphql" -const AES_DECRYPT_KEY = process.env.AES_DECRYPT_KEY ?? "0c3b25d92b38ae443229dd59ad34b85d" +export const AES_DECRYPT_KEY = + process.env.AES_DECRYPT_KEY ?? "0c3b25d92b38ae443229dd59ad34b85d" export const aesDecryptKey = Buffer.from(AES_DECRYPT_KEY, "hex") diff --git a/apps/boltcard/index.ts b/apps/boltcard/index.ts index 5d975255f86..c5db186a365 100644 --- a/apps/boltcard/index.ts +++ b/apps/boltcard/index.ts @@ -9,11 +9,14 @@ import { boltcardRouter } from "./router" import { lnurlw } from "./lnurlw" import { callback } from "./callback" import { createboltcard } from "./new" +import { wipe } from "./wipe" + import { createTable } from "./knex" lnurlw callback createboltcard +wipe await createTable() diff --git a/apps/boltcard/knex.ts b/apps/boltcard/knex.ts index 66736f23266..debaca9c1ae 100644 --- a/apps/boltcard/knex.ts +++ b/apps/boltcard/knex.ts @@ -63,7 +63,7 @@ export async function createTable() { table.string("token").notNullable() table.string("k0AuthKey").notNullable() - table.string("k2CmacKey").notNullable().index().unique() + table.string("k2CmacKey").notNullable() // .index().unique() enforcing uniqueness would ensure there is no reusage of keys table.string("k3").notNullable() table.string("k4").notNullable() }) diff --git a/apps/boltcard/lnurlw.ts b/apps/boltcard/lnurlw.ts index 02359a9799e..f06e093f27a 100644 --- a/apps/boltcard/lnurlw.ts +++ b/apps/boltcard/lnurlw.ts @@ -114,6 +114,9 @@ boltcardRouter.get("/ln", async (req: express.Request, res: express.Response) => } } + // TODO: check walletId and fail if not found + // this would improve the experience of the POS + const k1 = generateSecureRandomString(32) await insertk1({ k1, cardId: card.id }) diff --git a/apps/boltcard/new.ts b/apps/boltcard/new.ts index 3bd56f27806..fc941a6ec0e 100644 --- a/apps/boltcard/new.ts +++ b/apps/boltcard/new.ts @@ -40,8 +40,8 @@ boltcardRouter.get( // TODO: token validation? const oneTimeCode = randomHex() - const k0AuthKey = "0c3b25d92b38ae443229dd59ad34b85d" - const k2CmacKey = "b45775776cb224c75bcde7ca3704e933" + const k0AuthKey = randomHex() + const k2CmacKey = randomHex() const k3 = randomHex() const k4 = randomHex() @@ -71,13 +71,13 @@ interface NewCardResponse { PROTOCOL_NAME: string PROTOCOL_VERSION: number CARD_NAME: string - LNURLW_BASE: string - K0: string - K1: string - K2: string - K3: string - K4: string - UID_PRIVACY: string + lnurlw_base: string + k0: string + k1: string + k2: string + k3: string + k4: string + uid_privacy: string } boltcardRouter.get("/new", async (req: express.Request, res: express.Response) => { @@ -113,19 +113,21 @@ boltcardRouter.get("/new", async (req: express.Request, res: express.Response) = } const lnurlwBase = `${serverUrl}/ln` + .replace("http://", "lnurlw://") + .replace("https://", "lnurlw://") const k1DecryptKey = aesDecryptKey.toString("hex") const response: NewCardResponse = { PROTOCOL_NAME: "create_bolt_card_response", PROTOCOL_VERSION: 2, CARD_NAME: "dummy", - LNURLW_BASE: lnurlwBase, - K0: cardInit.k0AuthKey, - K1: k1DecryptKey, - K2: cardInit.k2CmacKey, - K3: cardInit.k3, - K4: cardInit.k4, - UID_PRIVACY: "Y", + lnurlw_base: lnurlwBase, + k0: cardInit.k0AuthKey, + k1: k1DecryptKey, + k2: cardInit.k2CmacKey, + k3: cardInit.k3, + k4: cardInit.k4, + uid_privacy: "Y", } res.status(200).json(response) diff --git a/apps/boltcard/wipe.ts b/apps/boltcard/wipe.ts new file mode 100644 index 00000000000..d1043dc8377 --- /dev/null +++ b/apps/boltcard/wipe.ts @@ -0,0 +1,59 @@ +import express from "express" + +import { boltcardRouter } from "./router" +import { fetchByCardId, fetchByOneTimeCode } from "./knex" +import { AES_DECRYPT_KEY } from "./config" + +boltcardRouter.get( + "/wipeboltcard", + async (req: express.Request, res: express.Response) => { + // should be pass with POST? not sure if this would be compatible + // with the wallet that can create cards + const cardId = req.query.cardId + const oneTimeCode = req.query.a + + if (!cardId && !oneTimeCode) { + res.status(400).send({ status: "ERROR", reason: "cardId missing" }) + return + } + // TODO authorization + + // TODO may be both on CardInit and Card table + let card + if (cardId) { + if (typeof cardId !== "string") { + res.status(400).send({ status: "ERROR", reason: "cardId is not a string" }) + return + } + + card = await fetchByCardId(cardId) + } else { + if (typeof oneTimeCode !== "string") { + res.status(400).send({ status: "ERROR", reason: "oneTimeCode is not a string" }) + return + } + + card = await fetchByOneTimeCode(oneTimeCode) + } + + if (!card) { + res.status(400).send({ status: "ERROR", reason: "card not found" }) + return + } + + res.json({ + status: "OK", + action: "wipe", + k0: card.k0AuthKey, + k1: AES_DECRYPT_KEY, + k2: card.k2CmacKey, + k3: card.k3, + k4: card.k4, + uid: card.uid, + version: 1, + }) + }, +) + +const wipe = "" +export { wipe }