Skip to content

Commit c77c403

Browse files
TaprootFreakbernd2022davidleomay
authored
Release: develop -> main (#2927)
* fix(security): disable unused urlencoded parser and patch CVE-2024-45590 & CVE-2025-15284 (#2925) - Add body-parser 1.20.3 override to fix CVE-2024-45590 (DoS via deep nesting) - Add qs ^6.14.1 override to fix CVE-2025-15284 (arrayLimit bypass) - Disable automatic bodyParser in NestFactory.create() to remove unused urlencoded attack surface Closes #2921 * [DEV-3526] Add automatic liquidity pipeline for ref payouts (#2672) * feat(ref-reward): Add automatic liquidity pipeline for ref payouts When ref reward payouts lack sufficient liquidity, automatically trigger the LiquidityManagement pipeline (similar to BuyCrypto). Changes: - Add liquidityPipeline relation to RefReward entity - Extend secureLiquidity() to check available liquidity - Start pipeline via liquidityService.buyLiquidity() when deficit detected - Set status to PENDING_LIQUIDITY before pipeline attempt (consistent with BuyCrypto) - Process pending rewards when pipeline completes - Reset to PREPARED on pipeline failure for automatic retry Flow: PREPARED → [liquidity check] → PENDING_LIQUIDITY → [pipeline complete] → READY_FOR_PAYOUT * refactor: improve consistency of liquidity pipeline implementation - Add pendingLiquidity() and resetToPrepared() entity methods to RefReward (consistent with existing entity method pattern like readyToPayout(), payingOut()) - Consolidate DB queries: load all rewards in single query instead of 3 separate - Fix error handling: try/catch per individual reward instead of per asset group (consistent with ref-reward-out.service.ts pattern) - Atomize pipeline updates: create pipeline first, then update rewards (avoids inconsistent state where rewards are PENDING_LIQUIDITY without pipeline) - Remove misleading 'consistent with BuyCrypto' comment * fix: change log level to warn for unexpected state Rewards in PENDING_LIQUIDITY without pipeline should never occur with atomic updates. This indicates legacy data or DB issues. * feat: refactoring * feat: added migration --------- Co-authored-by: David May <david.leo.may@gmail.com> * fix: exclude pending deposits from Kraken toKraken balance calculation (#2929) Kraken deposits with status='pending' (On Hold) were incorrectly being counted as "arrived" in the toKraken pending balance calculation. This caused the balance to show ~117k CHF less than actual, because: - 50k CHF On Hold - 30k CHF On Hold - 40k EUR On Hold (~37k CHF) These deposits have not yet been credited to the Kraken account and should not be subtracted from the pending-to-Kraken amount. Add status !== 'pending' filter to both CHF and EUR receiver exchange_tx filters in getFinancialDataLog(). * fix: prioritize user-provided refundTarget over pre-filled value (#2928) When processing refunds, user input (dto.refundTarget) should take priority over the pre-filled value (refundData.refundTarget). This allows customers to override the original IBAN when it's a Multi-Account IBAN that cannot be used for refunds. Changed: - BankTxReturn refund: dto.refundTarget ?? refundData.refundTarget - BuyCrypto bank refund: dto.refundTarget ?? refundData.refundTarget Previously the logic was reversed, ignoring user-provided values when the backend had a pre-filled refundTarget. --------- Co-authored-by: bernd2022 <104787072+bernd2022@users.noreply.github.com> Co-authored-by: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Co-authored-by: David May <david.leo.may@gmail.com>
2 parents 7e6a6ba + 0ddc4cf commit c77c403

10 files changed

Lines changed: 142 additions & 121 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @typedef {import('typeorm').MigrationInterface} MigrationInterface
3+
* @typedef {import('typeorm').QueryRunner} QueryRunner
4+
*/
5+
6+
/**
7+
* @class
8+
* @implements {MigrationInterface}
9+
*/
10+
module.exports = class RefRewardLiqPipeline1768325447128 {
11+
name = 'RefRewardLiqPipeline1768325447128'
12+
13+
/**
14+
* @param {QueryRunner} queryRunner
15+
*/
16+
async up(queryRunner) {
17+
await queryRunner.query(`ALTER TABLE "ref_reward" ADD "liquidityPipelineId" int`);
18+
await queryRunner.query(`ALTER TABLE "ref_reward" ADD CONSTRAINT "FK_0bdf973ad618dffd7a7c6c53dc8" FOREIGN KEY ("liquidityPipelineId") REFERENCES "liquidity_management_pipeline"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
19+
}
20+
21+
/**
22+
* @param {QueryRunner} queryRunner
23+
*/
24+
async down(queryRunner) {
25+
await queryRunner.query(`ALTER TABLE "ref_reward" DROP CONSTRAINT "FK_0bdf973ad618dffd7a7c6c53dc8"`);
26+
await queryRunner.query(`ALTER TABLE "ref_reward" DROP COLUMN "liquidityPipelineId"`);
27+
}
28+
}

package-lock.json

Lines changed: 47 additions & 103 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@
181181
"forceExit": true
182182
},
183183
"overrides": {
184+
"body-parser": "1.20.3",
185+
"qs": "^6.14.1",
184186
"request": {
185187
"form-data": "2.5.5"
186188
},

src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ async function bootstrap() {
4242
AppInsights.start();
4343
}
4444

45-
const app = await NestFactory.create(AppModule);
45+
const app = await NestFactory.create(AppModule, { bodyParser: false });
4646

4747
app.use(morgan('dev'));
4848
app.use(helmet());

src/subdomains/core/history/controllers/transaction.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ export class TransactionController {
447447
if (!dto.creditorData) throw new BadRequestException('Creditor data is required for bank refunds');
448448

449449
return this.bankTxReturnService.refundBankTx(transaction.targetEntity, {
450-
refundIban: refundData.refundTarget ?? dto.refundTarget,
450+
refundIban: dto.refundTarget ?? refundData.refundTarget,
451451
chargebackCurrency,
452452
creditorData: dto.creditorData,
453453
...refundDto,
@@ -476,7 +476,7 @@ export class TransactionController {
476476
if (!dto.creditorData) throw new BadRequestException('Creditor data is required for bank refunds');
477477

478478
return this.buyCryptoService.refundBankTx(transaction.targetEntity, {
479-
refundIban: refundData.refundTarget ?? dto.refundTarget,
479+
refundIban: dto.refundTarget ?? refundData.refundTarget,
480480
chargebackCurrency,
481481
creditorData: dto.creditorData,
482482
...refundDto,

src/subdomains/core/liquidity-management/entities/liquidity-management-pipeline.entity.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IEntity } from 'src/shared/models/entity';
22
import { Column, Entity, Index, JoinTable, ManyToOne, OneToMany } from 'typeorm';
33
import { BuyCrypto } from '../../buy-crypto/process/entities/buy-crypto.entity';
4+
import { RefReward } from '../../referral/reward/ref-reward.entity';
45
import {
56
LiquidityManagementExchanges,
67
LiquidityManagementOrderStatus,
@@ -28,6 +29,9 @@ export class LiquidityManagementPipeline extends IEntity {
2829
@OneToMany(() => BuyCrypto, (buyCrypto) => buyCrypto.liquidityPipeline)
2930
buyCryptos: BuyCrypto[];
3031

32+
@OneToMany(() => RefReward, (refReward) => refReward.liquidityPipeline)
33+
refRewards: RefReward[];
34+
3135
@OneToMany(() => LiquidityManagementOrder, (orders) => orders.pipeline)
3236
orders: LiquidityManagementOrder[];
3337

src/subdomains/core/referral/referral.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { forwardRef, Module } from '@nestjs/common';
22
import { TypeOrmModule } from '@nestjs/typeorm';
33
import { BlockchainModule } from 'src/integration/blockchain/blockchain.module';
44
import { SharedModule } from 'src/shared/shared.module';
5+
import { LiquidityManagementModule } from 'src/subdomains/core/liquidity-management/liquidity-management.module';
56
import { UserModule } from 'src/subdomains/generic/user/user.module';
67
import { DexModule } from 'src/subdomains/supporting/dex/dex.module';
78
import { NotificationModule } from 'src/subdomains/supporting/notification/notification.module';
@@ -32,6 +33,7 @@ import { RefRewardService } from './reward/services/ref-reward.service';
3233
NotificationModule,
3334
PricingModule,
3435
forwardRef(() => TransactionModule),
36+
LiquidityManagementModule,
3537
],
3638
controllers: [RefController, RefRewardController],
3739
providers: [

src/subdomains/core/referral/reward/ref-reward.entity.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.enum';
22
import { UpdateResult } from 'src/shared/models/entity';
3+
import { LiquidityManagementPipeline } from 'src/subdomains/core/liquidity-management/entities/liquidity-management-pipeline.entity';
34
import { UserData } from 'src/subdomains/generic/user/models/user-data/user-data.entity';
45
import { User } from 'src/subdomains/generic/user/models/user/user.entity';
56
import { Transaction } from 'src/subdomains/supporting/payment/entities/transaction.entity';
@@ -40,6 +41,9 @@ export class RefReward extends Reward {
4041
@JoinColumn()
4142
sourceTransaction?: Transaction;
4243

44+
@ManyToOne(() => LiquidityManagementPipeline, { nullable: true })
45+
liquidityPipeline?: LiquidityManagementPipeline;
46+
4347
//*** FACTORY METHODS ***//
4448

4549
readyToPayout(outputAmount: number): UpdateResult<RefReward> {

0 commit comments

Comments
 (0)