Skip to content

Commit 8c9184a

Browse files
committed
added some updates
1 parent cb27007 commit 8c9184a

28 files changed

Lines changed: 814 additions & 2473 deletions

File tree

backend/.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,15 @@ USDC_ISSUER=your_usdc_issuer
3131

3232
# x402 pay-per-request config
3333
# Charge applied to POST /api/create-payment and POST /api/sessions
34+
# Note: charged endpoints still require merchant API key auth.
3435
X402_PROVIDER_PUBLIC_KEY=your_stellar_public_key
3536
X402_JWT_SECRET=replace_with_random_secret
3637
X402_TOKEN_EXPIRY_SECONDS=60
3738
X402_CREATE_PAYMENT_AMOUNT=0.01
39+
# Optional: when false, x402 is opt-in per request via header/query.
40+
# true => every create-payment/sessions call is challenged when token missing
41+
# false => only requests with x-pluto-pricing-mode: x402 (or ?pricing_mode=x402) are challenged
42+
X402_ENFORCE_DEFAULT=false
3843

3944
# Webhook signing – shared secret used to produce Stellar-Signature: sha256=<hmac>
4045
# Generate a strong random value, e.g.: openssl rand -hex 32

backend/scripts/demoAgent.js

Lines changed: 0 additions & 144 deletions
This file was deleted.

backend/src/app.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ import prometheusRouter from "./routes/prometheus.js";
1616
import sep0001Router from "./routes/sep0001.js";
1717
import paymentDetailsRouter from "./routes/paymentDetails.js";
1818
import x402Router from "./routes/x402.js";
19-
import demoRouter from "./routes/demo.js";
19+
import authRouter from "./routes/auth.js";
2020

2121
import { requireApiKeyAuth } from "./lib/auth.js";
2222
import { isHorizonReachable } from "./lib/stellar.js";
2323
import { supabase } from "./lib/supabase.js";
2424
import { pool } from "./lib/db.js";
2525
import { x402Middleware } from "./middleware/x402.js";
26-
import { x402AuthBridge } from "./middleware/x402-auth.js";
2726

2827
import { idempotencyMiddleware } from "./lib/idempotency.js";
2928
import { setupSentryErrorHandler } from "./lib/sentry.js";
@@ -218,12 +217,14 @@ export async function createApp({ redisClient }) {
218217
const x402Provider = process.env.X402_PROVIDER_PUBLIC_KEY;
219218
const x402Enabled = Boolean(x402Provider && process.env.X402_JWT_SECRET);
220219
const x402CreatePaymentAmount = process.env.X402_CREATE_PAYMENT_AMOUNT || "0.01";
220+
const x402EnforceDefault = String(process.env.X402_ENFORCE_DEFAULT || "false").toLowerCase() === "true";
221221

222222
if (x402Enabled) {
223223
const requireCreatePaymentCharge = x402Middleware({
224224
amount: x402CreatePaymentAmount,
225225
recipient: x402Provider,
226226
memo_prefix: "pluto-create",
227+
enforceByDefault: x402EnforceDefault,
227228
});
228229

229230
app.use("/api/create-payment", requireCreatePaymentCharge);
@@ -232,13 +233,11 @@ export async function createApp({ redisClient }) {
232233

233234
app.use(
234235
"/api/create-payment",
235-
x402Enabled ? x402AuthBridge() : (_req, _res, next) => next(),
236236
requireApiKeyAuth(),
237237
idempotencyMiddleware
238238
);
239239
app.use(
240240
"/api/sessions",
241-
x402Enabled ? x402AuthBridge() : (_req, _res, next) => next(),
242241
requireApiKeyAuth(),
243242
idempotencyMiddleware
244243
);
@@ -249,6 +248,7 @@ export async function createApp({ redisClient }) {
249248

250249
app.use("/api", createPaymentsRouter({ verifyPaymentRateLimit }));
251250
app.use("/api", createMerchantsRouter({ merchantRegistrationRateLimit }));
251+
app.use("/api", authRouter);
252252
app.use("/api", metricsRouter);
253253
app.use("/api", webhooksRouter);
254254
app.use("/api/payments", paymentDetailsRouter); // NEW — GET /api/payments/:id
@@ -262,9 +262,6 @@ export async function createApp({ redisClient }) {
262262
// x402 pay-per-request verification (public — agents call this)
263263
app.use("/api", x402Router);
264264

265-
// Demo routes showing x402 in action
266-
app.use("/api", demoRouter);
267-
268265
// Sentry error handler — must come after all routes, before custom error handler
269266
setupSentryErrorHandler(app);
270267

backend/src/middleware/x402.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* 4. Client retries with X-Payment-Token: <jwt> → gets access
1212
*/
1313

14-
import { createHmac, randomUUID } from "node:crypto";
14+
import { randomUUID } from "node:crypto";
1515
import jwt from "jsonwebtoken";
1616

1717
const USDC_ISSUER = process.env.USDC_ISSUER ||
@@ -26,6 +26,7 @@ const USDC_ISSUER = process.env.USDC_ISSUER ||
2626
* @param {string} [config.asset] - Asset code, defaults to "USDC"
2727
* @param {string} [config.plutoVerifyUrl] - URL of /api/verify-x402
2828
* @param {string} [config.memo_prefix] - Memo prefix, defaults to "x402"
29+
* @param {boolean} [config.enforceByDefault] - If true, always challenge when token is absent
2930
*/
3031
export function x402Middleware(config) {
3132
const {
@@ -34,6 +35,7 @@ export function x402Middleware(config) {
3435
asset = "USDC",
3536
plutoVerifyUrl = `${process.env.PAYMENT_LINK_BASE?.replace(":3000", ":4000") || "http://localhost:4000"}/api/verify-x402`,
3637
memo_prefix = "x402",
38+
enforceByDefault = false,
3739
} = config;
3840

3941
const jwtSecret = process.env.X402_JWT_SECRET;
@@ -43,6 +45,13 @@ export function x402Middleware(config) {
4345

4446
return function requirePayment(req, res, next) {
4547
const token = req.headers["x-payment-token"];
48+
const modeHeader = String(req.headers["x-pluto-pricing-mode"] || "")
49+
.trim()
50+
.toLowerCase();
51+
const modeQueryRaw = req.query?.pricing_mode;
52+
const modeQuery =
53+
typeof modeQueryRaw === "string" ? modeQueryRaw.trim().toLowerCase() : "";
54+
const requestedX402 = modeHeader === "x402" || modeQuery === "x402";
4655

4756
if (token) {
4857
try {
@@ -54,6 +63,13 @@ export function x402Middleware(config) {
5463
}
5564
}
5665

66+
// Dual-mode behavior:
67+
// - Subscription/API-key mode: pass through
68+
// - x402 mode: challenge with 402
69+
if (!enforceByDefault && !requestedX402) {
70+
return next();
71+
}
72+
5773
const requestId = randomUUID().replace(/-/g, "");
5874
const separator = "-";
5975
const maxMemoBytes = 28;

backend/src/routes/auth.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ router.post("/auth/login", async (req, res, next) => {
9393
}
9494
});
9595

96-
96+
/*
9797
* post:
9898
* summary: Generate a SEP-0010 challenge transaction
9999
* tags: [Auth]

0 commit comments

Comments
 (0)