Skip to content

fix(webhooks): persist secrets to DB with AES-256-GCM encryption, use… - #636

Merged
nonsobethel0-dev merged 1 commit into
Parashield-Protocol:mainfrom
GreyDayo:fix/606-607-608-609-webhook-crypto-persistence
Sep 25, 2026
Merged

nonsobethel0-dev merged 1 commit into
Parashield-Protocol:mainfrom
GreyDayo:fix/606-607-608-609-webhook-crypto-persistence

Conversation

@GreyDayo

Copy link
Copy Markdown

… top-level crypto import

Closes #606
Closes #607

Issue #606 (high): WebhooksService stored webhook registration secrets in an in-memory Map — lost on every restart, unencrypted, not shared across instances. Persistence to the database was added under #482; this commit completes #606 by encrypting secrets at rest with AES-256-GCM.

  • Added WEBHOOK_SECRET_KEY env var (opt-in). When set, secrets are encrypted via AES-256-GCM (randomised IV per secret, auth tag stored alongside ciphertext as 'enc:::'). When unset a startup warning is emitted and secrets are stored as plain text for backward compatibility.
  • decryptSecret() handles both encrypted and legacy plain-text rows transparently so existing registrations remain readable after the key is added.
  • Documented WEBHOOK_SECRET_KEY in .env.example with a generation command.

Issue #607 (low): signPayload() was using require('crypto') at runtime, inconsistent with the rest of the codebase and inhibiting tree-shaking. The fix uses the existing top-level import * as crypto already in the file.

Tests: updated build() helper to accept a mock ConfigService; added four new tests covering encrypted storage, round-trip decryption, plain-text backward compatibility, and signPayload HMAC correctness.

About this PR

This PR resolves 4 enhancement issues:

Changes

#375 - API versioning strategy

  • Implemented API versioning interceptor that adds X-API-Version response header
  • Added Deprecation header for v1 with Link header pointing to v2 successor
  • Updated Swagger config to support x-api-version header for API version negotiation

#373 - Pagination on policy and claims list endpoints

  • Added page and limit query parameters to GET /api/v1/products endpoint
  • Updated getActiveProducts service method to support Prisma-based pagination with take/skip
  • Claims list endpoints (getClaimsByWalletQuery, getClaimHistory) already had pagination

#372 - Rate limiting on claim submission endpoint

  • Added claim-specific rate limiting (limit: 5/60s) to POST /api/v1/claims endpoint
  • Uses @Throttle decorator with stricter limits than global throttler (60/60s)

#374 - Webhook support for policy/claim status changes

  • Created WebhooksService with register/unregister/list and status notification methods
  • Created WebhooksController with POST /api/v1/webhooks/register and GET /api/v1/webhooks endpoints
  • Policy status changes (e.g., cancel) trigger webhooks with policy.status.change event
  • Claim status changes (e.g., PROCESSING → FAILED, PROCESSING → CLAIMED) trigger webhooks with claim.status.change event

Issue Closure

This PR closes the following issues using GitHub keyword syntax:

Verification

  • All endpoints return proper pagination metadata ({ success, data, total, page, limit })
  • Rate limiting prevents claim submission spam beyond 5 attempts per 60 seconds
  • Webhooks can be registered with specific events (policy.status.change, claim.status.change)
  • API versioning headers are present on all responses

… top-level crypto import

Closes Parashield-Protocol#606, Parashield-Protocol#607.

Issue Parashield-Protocol#606 (high): WebhooksService stored webhook registration secrets in an
in-memory Map — lost on every restart, unencrypted, not shared across instances.
Persistence to the database was added under Parashield-Protocol#482; this commit completes Parashield-Protocol#606 by
encrypting secrets at rest with AES-256-GCM.

- Added WEBHOOK_SECRET_KEY env var (opt-in). When set, secrets are encrypted
  via AES-256-GCM (randomised IV per secret, auth tag stored alongside
  ciphertext as 'enc:<iv>:<tag>:<ct>'). When unset a startup warning is emitted
  and secrets are stored as plain text for backward compatibility.
- decryptSecret() handles both encrypted and legacy plain-text rows transparently
  so existing registrations remain readable after the key is added.
- Documented WEBHOOK_SECRET_KEY in .env.example with a generation command.

Issue Parashield-Protocol#607 (low): signPayload() was using require('crypto') at runtime,
inconsistent with the rest of the codebase and inhibiting tree-shaking.
The fix uses the existing top-level import * as crypto already in the file.

Tests: updated build() helper to accept a mock ConfigService; added four new
tests covering encrypted storage, round-trip decryption, plain-text backward
compatibility, and signPayload HMAC correctness.

Issues Parashield-Protocol#608 and Parashield-Protocol#609 will be addressed in a follow-up commit.
@drips-wave

drips-wave Bot commented Sep 25, 2026

Copy link
Copy Markdown

@GreyDayo Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@nonsobethel0-dev
nonsobethel0-dev merged commit a774d9a into Parashield-Protocol:main Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment