Skip to content

Fix/issues 245 246 247 248 - #440

Open
pugsley76 wants to merge 3 commits into
arflexx:mainfrom
pugsley76:fix/issues-245-246-247-248
Open

pugsley76 wants to merge 3 commits into
arflexx:mainfrom
pugsley76:fix/issues-245-246-247-248

Conversation

@pugsley76

Copy link
Copy Markdown
Contributor

fix(#245,#246,#247,#248): security hardening, withdraw queue, KYC S3 storage

#247 — JWT_SECRET fail-fast validation

  • validateEnv.ts: reject secrets shorter than 32 chars at startup with a
    descriptive error and generation hint
  • auth.ts: replace all process.env["JWT_SECRET"]! non-null assertions with
    a getJwtSecret() helper that throws clearly if the secret is absent
  • tests/validateEnv.test.ts: 7 cases covering absent, too-short,
    exactly-32-char, and assertEnvValid throw behaviour

#245 — Client-side signing model documented

  • docs/security.md: add full client-side signing model section explaining
    the /buy/prepare → /wallet/unlock → /buy flow; buyerSecretKey is gone
  • contracts/THREAT_MODEL.md: add threat [server] - Implement OTP Authentication API Endpoints #7 documenting the original
    secret-in-request-body vulnerability and its mitigation

#246 — Withdrawal stub replaced with proper async implementation

  • 010_withdrawals.sql: withdrawals table with pending/processing/completed/
    failed state machine, idempotency reference, updated_at trigger
  • process-withdrawal.ts: background processor — creates Paystack recipient,
    initiates transfer, moves record to processing, records ledger debit;
    exports applyTransferSuccess / applyTransferFailed for webhook settlement
  • paystackWebhookHandlers.ts: applyTransferEvent dispatcher for
    transfer.success / transfer.failed / transfer.reversed
  • process-paystack-webhook.ts: route transfer events to applyTransferEvent
  • jobs/queue.ts: add process-withdrawal to JOB_QUEUES
  • jobs/index.ts: register processor, re-export type
  • routes/wallet.ts: POST /withdraw returns 202 Accepted; validates
    fiat_balance, creates withdrawal record, deducts balance, enqueues job —
    all inside a single DB transaction

#248 — KYC file storage abstracted behind StorageProvider interface

…t-side signing docs

Issue arflexx#247:
- validateEnv.ts: add JWT_SECRET_MIN_LEN=32 check so a weak secret is
  rejected at startup with an actionable error message, not silently
  accepted and then blown up inside jsonwebtoken at runtime
- auth.ts: replace all four process.env['JWT_SECRET']! non-null assertions
  with a getJwtSecret() helper that throws a descriptive error if the
  secret is somehow absent at call time (second line of defence)
- __tests__/validateEnv.test.ts: 7 cases covering absent secret, too-short
  secret, exactly-32-char secret, assertEnvValid throw behaviour, and
  ENCRYPTION_KEY format validation

Issue arflexx#245 (docs):
- docs/security.md: add 'Stellar Key Handling and Client-Side Signing Model'
  section explaining the /buy/prepare -> /wallet/unlock -> /buy flow and
  explicitly stating that buyerSecretKey is not accepted anywhere
- contracts/THREAT_MODEL.md: add threat arflexx#7 documenting the original
  secret-in-request-body vulnerability and the implemented mitigation
…ementation

- 010_withdrawals.sql: new withdrawals table with pending/processing/
  completed/failed state machine, idempotency reference column, and an
  updated_at trigger
- process-withdrawal.ts: background processor that creates a Paystack
  transfer recipient, initiates the transfer, moves the record to
  'processing', and records a debit in the transactions ledger.
  Exports applyTransferSuccess / applyTransferFailed for webhook use
- paystackWebhookHandlers.ts: import and re-export the two transfer
  settlement helpers; add applyTransferEvent dispatcher
- process-paystack-webhook.ts: route transfer.success / transfer.failed /
  transfer.reversed events to applyTransferEvent
- jobs/queue.ts: add 'process-withdrawal' to JOB_QUEUES const
- jobs/index.ts: register processWithdrawalProcessor; re-export
  ProcessWithdrawalData type
- routes/wallet.ts: POST /withdraw refactored from a synchronous inline
  Paystack call returning 200 to a 202 Accepted response that validates
  fiat_balance, creates the withdrawal record, deducts the balance, and
  enqueues the background job atomically inside a DB transaction
…terface

- storage/StorageProvider.ts: interface with upload(key, stream, opts) and
  delete(key) — the only contract application code touches
- storage/S3StorageProvider.ts: production implementation using
  @aws-sdk/client-s3 + @aws-sdk/lib-storage; supports any S3-compatible
  store via S3_ENDPOINT override (MinIO, R2, etc.); private ACL +
  AES256 server-side encryption by default
- storage/LocalStorageProvider.ts: dev fallback (STORAGE_PROVIDER=local)
  that preserves the old uploads/kyc/ write behaviour without any AWS dep
- storage/index.ts: singleton factory — selects backend from
  STORAGE_PROVIDER env var, lazy-imports the concrete class
- routes/kyc.ts: rewritten to pipe busboy file stream directly into
  StorageProvider.upload() (no createWriteStream / temp disk writes);
  kyc_document_path now stores the object-store key, not a filesystem path;
  partial upload cleanup on file-too-large and field validation errors
- config/validateEnv.ts: add S3_BUCKET + S3_REGION to required-when-s3
  check; add STORAGE_PROVIDER / S3_ENDPOINT to OPTIONAL_ENV_VARS
@pugsley76
pugsley76 requested a review from dark-sarge as a code owner October 17, 2026 10:24
@drips-wave

drips-wave Bot commented Oct 17, 2026

Copy link
Copy Markdown

@pugsley76 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment