Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "1.0.0",
"main": "index.js",
"scripts": {
"dev": "ts-node-dev --respawn src/index.ts",
"dev": "ts-node-dev --respawn --files src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"test": "jest",
Expand Down
343 changes: 343 additions & 0 deletions src/controllers/MultiAssetPaymentController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
import { Request, Response } from "express";
import { AssetConfigurationService } from "../services/AssetConfigurationService";
import { AssetPriceService } from "../services/AssetPriceService";
import { MultiAssetPaymentService } from "../services/MultiAssetPaymentService";
import { AppError } from "../utils/AppError";
import logger from "../utils/logger";

export class MultiAssetPaymentController {
private assetConfigService: AssetConfigurationService;
private assetPriceService: AssetPriceService;
private multiAssetPaymentService: MultiAssetPaymentService;

constructor() {
this.assetConfigService = new AssetConfigurationService();
this.assetPriceService = new AssetPriceService();
this.multiAssetPaymentService = new MultiAssetPaymentService();
}

createAssetConfig = async (req: Request, res: Response): Promise<void> => {
try {
const {
merchantId,
asset,
isEnabled,
minAmount,
maxAmount,
priority,
autoConvert,
settlementAsset,
} = req.body;

const config = await this.assetConfigService.createAssetConfig({
merchantId,
assetCode: asset.code,
assetIssuer: asset.issuer || null,
isEnabled,
minAmount,
maxAmount,
priority,
autoConvert,
settlementAssetCode: settlementAsset?.code || null,
settlementAssetIssuer: settlementAsset?.issuer || null,
});

res.status(201).json({
success: true,
message: "Asset configuration created successfully",
data: config,
});
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({
success: false,
message: error.message,
});
} else {
logger.error("Error creating asset config:", error);
res.status(500).json({
success: false,
message: "Internal server error",
});
}
}
};

updateAssetConfig = async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;
const { merchantId } = req.body;
const {
isEnabled,
minAmount,
maxAmount,
priority,
autoConvert,
settlementAsset,
} = req.body;

const config = await this.assetConfigService.updateAssetConfig(
id,
merchantId,
{
isEnabled,
minAmount,
maxAmount,
priority,
autoConvert,
settlementAssetCode: settlementAsset?.code || null,
settlementAssetIssuer: settlementAsset?.issuer || null,
},
);
Comment on lines +66 to +91

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use authenticated merchant ID instead of request body.

The updateAssetConfig method takes merchantId from req.body, but the route uses authenticateMerchant middleware which sets req.merchant. Using the body value could allow a merchant to attempt modifying another merchant's configuration (though the service layer may prevent this). For defense in depth, use the authenticated merchant's ID.

♻️ Suggested fix
   updateAssetConfig = async (req: Request, res: Response): Promise<void> => {
     try {
       const { id } = req.params;
-      const { merchantId } = req.body;
+      const merchantId = (req as any).merchant?.id;
       const {
         isEnabled,

Apply similar changes to deleteAssetConfig and createAssetConfig.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
updateAssetConfig = async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;
const { merchantId } = req.body;
const {
isEnabled,
minAmount,
maxAmount,
priority,
autoConvert,
settlementAsset,
} = req.body;
const config = await this.assetConfigService.updateAssetConfig(
id,
merchantId,
{
isEnabled,
minAmount,
maxAmount,
priority,
autoConvert,
settlementAssetCode: settlementAsset?.code || null,
settlementAssetIssuer: settlementAsset?.issuer || null,
},
);
updateAssetConfig = async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;
const merchantId = (req as any).merchant?.id;
const {
isEnabled,
minAmount,
maxAmount,
priority,
autoConvert,
settlementAsset,
} = req.body;
const config = await this.assetConfigService.updateAssetConfig(
id,
merchantId,
{
isEnabled,
minAmount,
maxAmount,
priority,
autoConvert,
settlementAssetCode: settlementAsset?.code || null,
settlementAssetIssuer: settlementAsset?.issuer || null,
},
);
🤖 Prompt for AI Agents
In `@src/controllers/MultiAssetPaymentController.ts` around lines 66 - 91, The
updateAssetConfig handler is reading merchantId from req.body which allows
spoofing; change it to use the authenticated merchant ID provided by the
authenticateMerchant middleware (req.merchant.id) when calling
this.assetConfigService.updateAssetConfig (and likewise for createAssetConfig
and deleteAssetConfig) so the controller always passes the verified merchant id
instead of any value from req.body; locate references to merchantId in
updateAssetConfig, createAssetConfig, and deleteAssetConfig and replace them
with the authenticated req.merchant.id before invoking the service.


res.status(200).json({
success: true,
message: "Asset configuration updated successfully",
data: config,
});
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({
success: false,
message: error.message,
});
} else {
logger.error("Error updating asset config:", error);
res.status(500).json({
success: false,
message: "Internal server error",
});
}
}
};

deleteAssetConfig = async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;
const { merchantId } = req.body;

await this.assetConfigService.deleteAssetConfig(id, merchantId);

res.status(200).json({
success: true,
message: "Asset configuration deleted successfully",
});
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({
success: false,
message: error.message,
});
} else {
logger.error("Error deleting asset config:", error);
res.status(500).json({
success: false,
message: "Internal server error",
});
}
}
};

getMerchantAssetConfigs = async (
req: Request,
res: Response,
): Promise<void> => {
try {
const { merchantId } = req.params;

const configs =
await this.assetConfigService.getMerchantAssetConfigs(merchantId);

res.status(200).json({
success: true,
data: configs,
});
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({
success: false,
message: error.message,
});
} else {
logger.error("Error fetching merchant configs:", error);
res.status(500).json({
success: false,
message: "Internal server error",
});
}
}
};

getExchangeRate = async (req: Request, res: Response): Promise<void> => {
try {
const { sourceAsset, destinationAsset, amount } = req.body;

const quote = await this.assetPriceService.getExchangeRate(
sourceAsset.code,
sourceAsset.issuer,
destinationAsset.code,
destinationAsset.issuer,
amount,
);

res.status(200).json({
success: true,
data: quote,
});
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({
success: false,
message: error.message,
});
} else {
logger.error("Error getting exchange rate:", error);
res.status(500).json({
success: false,
message: "Internal server error",
});
}
}
};

executePathPayment = async (req: Request, res: Response): Promise<void> => {
try {
const {
merchantId,
sourceAddress,
destinationAddress,
sourceAsset,
destinationAsset,
amount,
sourceSecret,
memo,
} = req.body;

const result = await this.multiAssetPaymentService.executePathPayment({
merchantId,
sourceAddress,
destinationAddress,
sourceAssetCode: sourceAsset.code,
sourceAssetIssuer: sourceAsset.issuer,
destAssetCode: destinationAsset?.code || sourceAsset.code,
destAssetIssuer: destinationAsset?.issuer || sourceAsset.issuer,
amount,
sourceSecret,
memo,
});

res.status(200).json({
success: true,
message: "Path payment executed successfully",
data: result,
});
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({
success: false,
message: error.message,
});
} else {
logger.error("Error executing path payment:", error);
res.status(500).json({
success: false,
message: "Internal server error",
});
}
}
};

estimatePayment = async (req: Request, res: Response): Promise<void> => {
try {
const { merchantId, sourceAsset, destinationAsset, amount } = req.body;

const estimate = await this.multiAssetPaymentService.estimatePayment({
merchantId,
sourceAssetCode: sourceAsset.code,
sourceAssetIssuer: sourceAsset.issuer,
destAssetCode: destinationAsset.code,
destAssetIssuer: destinationAsset.issuer,
amount,
});

res.status(200).json({
success: true,
data: estimate,
});
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({
success: false,
message: error.message,
});
} else {
logger.error("Error estimating payment:", error);
res.status(500).json({
success: false,
message: "Internal server error",
});
}
}
};

getSupportedAssetPairs = async (
req: Request,
res: Response,
): Promise<void> => {
try {
const { merchantId } = req.params;

const pairs =
await this.multiAssetPaymentService.getSupportedAssetPairs(merchantId);

res.status(200).json({
success: true,
data: pairs,
});
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({
success: false,
message: error.message,
});
} else {
logger.error("Error getting supported pairs:", error);
res.status(500).json({
success: false,
message: "Internal server error",
});
}
}
};

getOrderbook = async (req: Request, res: Response): Promise<void> => {
try {
const { sourceAsset, destinationAsset } = req.body;

const orderbook = await this.assetPriceService.getOrderbook(
sourceAsset.code,
sourceAsset.issuer,
destinationAsset.code,
destinationAsset.issuer,
);

res.status(200).json({
success: true,
data: orderbook,
});
} catch (error) {
if (error instanceof AppError) {
res.status(error.statusCode).json({
success: false,
message: error.message,
});
} else {
logger.error("Error getting orderbook:", error);
res.status(500).json({
success: false,
message: "Internal server error",
});
}
}
};
}
2 changes: 1 addition & 1 deletion src/controllers/RateLimitController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Request, Response } from "express";
import RateLimitMonitoringService from "../services/rateLimitMonitoring.service";
import rateLimitConfigService from "../services/rateLimitConfigService";
import whitelistBlacklistService from "../services/whitelistBlacklistService";
import { WhitelistType } from "src/entities/RateLimitWhiteList";
import { WhitelistType } from "../entities/RateLimitWhiteList";
import { BlacklistType, BlacklistReason } from "../entities/RateLimitBlacklist";
import logger from "../utils/logger";

Expand Down
Loading