diff --git a/apps/boltcard/app/api/activate/route.ts b/apps/boltcard/app/api/activate/route.ts index 1b43d4ec334..f5cb5eccdf3 100644 --- a/apps/boltcard/app/api/activate/route.ts +++ b/apps/boltcard/app/api/activate/route.ts @@ -4,6 +4,7 @@ import { aesDecryptKey, serverUrl } from "@/services/config" import { fetchByOneTimeCode } from "@/services/db/card-init" interface ActivateCardResponse { + warning: boolean protocol_name: string protocol_version: number card_name: string @@ -36,11 +37,10 @@ export async function GET(req: NextRequest) { ) } + let warningReusedCode = false + if (cardKeysSetup.status !== "init") { - return NextResponse.json( - { status: "ERROR", reason: "code has already been used" }, - { status: 400 }, - ) + warningReusedCode = true } const lnurlwBase = `${serverUrl}/api/ln` @@ -50,6 +50,7 @@ export async function GET(req: NextRequest) { const k1DecryptKey = aesDecryptKey.toString("hex") const response: ActivateCardResponse = { + warning: warningReusedCode, protocol_name: "create_bolt_card_response", protocol_version: 2, card_name: "", diff --git a/apps/boltcard/app/api/create/route.ts b/apps/boltcard/app/api/create/route.ts index 228ed924254..8f18a75105e 100644 --- a/apps/boltcard/app/api/create/route.ts +++ b/apps/boltcard/app/api/create/route.ts @@ -46,7 +46,7 @@ export async function GET(req: NextRequest) { } const apiActivationUrl = `${serverUrl}/api/activate?a=${oneTimeCode}` - const uiActivationUrl = `${serverUrl}/card/activate?a=${oneTimeCode}` + const uiActivationUrl = `${serverUrl}/card/activate/${oneTimeCode}` return NextResponse.json({ status: "OK", apiActivationUrl, diff --git a/apps/boltcard/app/card/[id]/page.tsx b/apps/boltcard/app/card/[id]/page.tsx index 8b7ef9b6c68..3c1b0001019 100644 --- a/apps/boltcard/app/card/[id]/page.tsx +++ b/apps/boltcard/app/card/[id]/page.tsx @@ -1,16 +1,30 @@ -import { serverApi } from "@/services/config" +import QRCode from "qrcode" +import Image from "next/image" + +import { isAdmin, serverUrl } from "@/services/config" export default async function Card({ params }: { params: { id: string } }) { const { id } = params - const cardApi = `${serverApi}/card/id/${id}` + const cardApi = `${serverUrl}/api/card/id/${id}` const cardResult = await fetch(cardApi, { cache: "no-store" }) const cardInfo = await cardResult.json() - const transactionsApi = `${serverApi}/card/id/${id}/transactions` - const transactionsResult = await fetch(transactionsApi) + const transactionsApi = `${serverUrl}/api/card/id/${id}/transactions` + const transactionsResult = await fetch(transactionsApi, { cache: "no-store" }) const transactionInfo = await transactionsResult.json() + let qrCode = "" + + if (isAdmin) { + const wipeApi = `${serverUrl}/api/wipe?cardId=${id}` + const wipeResult = await fetch(wipeApi, { cache: "no-store" }) + const wipeInfo = await wipeResult.json() + + // generate QR code + qrCode = await QRCode.toDataURL(JSON.stringify(wipeInfo), { width: 400 }) + } + return (

boltcard

@@ -63,12 +77,28 @@ export default async function Card({ params }: { params: { id: string } }) { Fee: {tx.settlementDisplayFee}{" "} {tx.settlementDisplayCurrency} - {/* Add more fields as required */} ))} + +
+

Wipe Card:

+

+ Warning: This will wipe the card and remove all funds. This + action cannot be undone. +

+

+ {"qr +

+
) } diff --git a/apps/boltcard/app/card/activate/[a]/page.tsx b/apps/boltcard/app/card/activate/[a]/page.tsx new file mode 100644 index 00000000000..a28b3bebc35 --- /dev/null +++ b/apps/boltcard/app/card/activate/[a]/page.tsx @@ -0,0 +1,33 @@ +import QRCode from "qrcode" +import Image from "next/image" + +import { serverUrl } from "@/services/config" + +export default async function ActivateCard({ params }: { params: { a: string } }) { + const { a } = params + + const url = `${serverUrl}/api/activate/?a=${a}` + const res = await fetch(url, { cache: "no-store" }) + const activationParams = await res.json() + const warning = activationParams.warning + + const qrCode = await QRCode.toDataURL(url, { width: 400 }) + + return ( + <> +
+

Activate Card

+ {warning && ( +

{"card should not be programmed twice with the same sets of keys"}

+ )} + {"qr +
+ + ) +} diff --git a/apps/boltcard/app/card/activate/page.tsx b/apps/boltcard/app/card/activate/page.tsx deleted file mode 100644 index 102b7054582..00000000000 --- a/apps/boltcard/app/card/activate/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { serverApi } from "@/services/config" - -export default async function ActivateCard({ params }: { params: { id: string } }) { - const { id } = params - - const cardApi = `${serverApi}/card/id/${id}` - const cardResult = await fetch(cardApi, { cache: "no-store" }) - const cardInfo = await cardResult.json() - - const transactionsApi = `${serverApi}/card/id/${id}/transactions` - const transactionsResult = await fetch(transactionsApi) - const transactionInfo = await transactionsResult.json() -} diff --git a/apps/boltcard/bats/e2e-test.bats b/apps/boltcard/bats/e2e-test.bats index ad169a0abb6..587cbe58e3a 100644 --- a/apps/boltcard/bats/e2e-test.bats +++ b/apps/boltcard/bats/e2e-test.bats @@ -14,13 +14,13 @@ random_phone() { export TOKEN_ALICE=$(read_value "alice") RESPONSE=$(curl -s "http://localhost:3000/api/create?token=${TOKEN_ALICE}") - CALLBACK_URL=$(echo $RESPONSE | jq -r '.apiActivationUrl') + CALLBACK_API_URL=$(echo $RESPONSE | jq -r '.apiActivationUrl') + CALLBACK_UI_URL=$(echo $RESPONSE | jq -r '.uiActivationUrl') - # echo "CALLBACK_URL: $CALLBACK_URL" - # exit 1 + # TODO: test CALLBACK_UI_URL # Making the follow-up curl request - RESPONSE=$(curl -s "${CALLBACK_URL}") + RESPONSE=$(curl -s "${CALLBACK_API_URL}") echo "$RESPONSE" [[ $(echo $RESPONSE | jq -r '.protocol_name') == "create_bolt_card_response" ]] || exit 1 @@ -86,6 +86,12 @@ random_phone() { [[ $(echo $result | jq -r '.status') == "OK" ]] || exit 1 } +@test "card ui" { + cardId=$(read_value "cardId") + http_status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/card/${cardId}) + [[ "$http_status" -eq 200 ]] || exit 1 +} + @test "wipecard" { cardId=$(read_value "cardId") result=$(curl -s http://localhost:3000/api/wipe?cardId=${cardId}) diff --git a/apps/boltcard/bun.lockb b/apps/boltcard/bun.lockb index 8a223069851..36fb35b8225 100755 Binary files a/apps/boltcard/bun.lockb and b/apps/boltcard/bun.lockb differ diff --git a/apps/boltcard/package.json b/apps/boltcard/package.json index f921d43f195..bc0ca655e27 100644 --- a/apps/boltcard/package.json +++ b/apps/boltcard/package.json @@ -26,19 +26,21 @@ "node-aes-cmac": "^0.1.1", "pg": "^8.11.3", "postcss": "8.4.29", + "qrcode": "^1.5.3", "react": "18.2.0", "react-dom": "18.2.0", "tailwindcss": "3.3.3", "typescript": "5.2.2" }, "devDependencies": { - "@types/pg": "^8.10.2", - "encoding": "^0.1.13", "@graphql-codegen/add": "^5.0.0", "@graphql-codegen/cli": "^5.0.0", "@graphql-codegen/client-preset": "^4.1.0", "@graphql-codegen/typescript": "^4.0.1", "@graphql-codegen/typescript-operations": "^4.0.1", - "@graphql-codegen/typescript-react-apollo": "^3.3.7" + "@graphql-codegen/typescript-react-apollo": "^3.3.7", + "@types/pg": "^8.10.2", + "@types/qrcode": "^1.5.2", + "encoding": "^0.1.13" } } diff --git a/apps/boltcard/services/config.ts b/apps/boltcard/services/config.ts index 701f8972451..c49d13f24d7 100644 --- a/apps/boltcard/services/config.ts +++ b/apps/boltcard/services/config.ts @@ -9,3 +9,5 @@ export const AES_DECRYPT_KEY = export const aesDecryptKey = Buffer.from(AES_DECRYPT_KEY, "hex") export const lightningDomain = process.env.LIGHTNING_DOMAIN ?? "localhost" + +export const isAdmin = true