Repository navigation
feat: payout batching, money-movement audit, anomaly detection, hardened JWT (#1008 #1011 #1013 #1014) - #1301
Merged
github-actions[bot] merged 1 commit intoOct 17, 2026
Conversation
…, hardened JWT tokens Payout scheduling and batching (Smartdevs17#1008) - PayoutBatcher schedules individual payouts and, when due, groups them by provider and asset into batches sent in one provider call each. - Atomic per call: a batch settles every item under one transaction hash or fails every item; partial acknowledgements count as failure. - Failures are isolated per batch and retried with backoff up to maxAttempts. - Batch and run reports show status, items, totals and fees saved. A worker runs due batches on an interval. Money-movement audit log (Smartdevs17#1011) - MoneyMovementAuditLog appends hash-chained, frozen entries recording actor, amount, asset and reference for every balance change, reservation, approval, rejection, settlement, failure and release in the payout engine and batcher. Compatible with verifyAuditChain, so edits, deletions and reordering are detected. - Compliance queries by actor, merchant, operation, reference, asset, amount range and time window, plus GET /payouts/audit and /payouts/audit/verify. Payout anomaly detection (Smartdevs17#1013) - PayoutAnomalyDetector flags single-payout size, count/amount velocity and deviation from the merchant baseline. Thresholds come from PAYOUT_ANOMALY_* env vars and can be updated at runtime by admins. - Every flag is emitted to alert subscribers. Flagged engine batches always need approval and cannot be approved until reviewed; flagged scheduled payouts are held out of batches. - Review path: confirm or mark false positive (optionally suppressing the rule for the merchant for a bounded time); false positives release held payouts, confirmations cancel them. Hardened JWT handling (Smartdevs17#1014) - Access tokens are now HS256 JWTs (15 min default, clamped to 1 min-1 h) instead of unverifiable opaque strings. The algorithm is pinned (none, asymmetric and other HMAC algorithms rejected), iss/aud/sub/jti/iat/exp are required, over-long lifetimes are rejected and signatures are compared in constant time. Supports key rotation via JWT_SECRET_PREVIOUS and refuses to run in production without JWT_SECRET. - Refresh rotation issues family-bound access tokens. A revocation list (local cache mirrored to Redis) is enforced by token-auth, WebSocket auth and the refresh routes, and is fed by logout, refresh-token replay and sign-out-everywhere. Also fixes a syntax error in middleware/logger.ts that stopped every module importing it (including token-auth) from loading. Closes Smartdevs17#1008 Closes Smartdevs17#1011 Closes Smartdevs17#1013 Closes Smartdevs17#1014
|
@JamesVictor-O is attempting to deploy a commit to the smartdevs17's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@JamesVictor-O 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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves four payout and auth issues in
backend/.#1008 — Payout scheduling and batching
New
services/payouts/payout-batcher.ts:scheduledFortime. A worker started inindex.ts, orPOST /payouts/scheduled/run, sends the ones that are due.PAYOUT_BATCH_MAX_SIZE. Each chunk is one provider call.maxAttemptsis reached.GET /payouts/batches,/batches/:idand/batches/runs/:runIdshow status, items, totals by asset, and fees saved by batching (PAYOUT_PROVIDER_FEE_PER_CALL).#1011 — Audit logging for all money-movement operations
New
audit/money-movement-audit.ts:ImmutableAuditLogEntryandcomputeAuditHashformat, soverifyAuditChainand its tamper alerts work unchanged. Entries are returned as frozen copies. Editing, deleting or reordering an entry is detected.GET /payouts/auditandGET /payouts/audit/verify. An optional durable store (ImmutableAuditStore) is written to in order, without blocking the payout.#1013 — Anomaly detection for unusual payout volume
New
services/payouts/payout-anomaly-detector.ts:PAYOUT_ANOMALY_*env vars or at runtime withPUT /payouts/anomalies/config(admin only, validated as a whole).GET /payouts/anomaliesandPOST /payouts/anomalies/:id/reviewwithconfirmedorfalse_positive. A false positive can suppress that rule for that merchant for a limited time (capped at 30 days).#1014 — Harden JWT handling and token lifetimes
Access tokens used to be random
at_…strings that were only checked for shape, so any string in the right format was accepted and nothing could be revoked. Newauth/jwt.tsandauth/access-token-revocation.ts:JWT_ACCESS_TOKEN_TTL_SECONDS), clamped to 1 minute–1 hour. A signed token whose own lifetime exceeds the cap is rejected.HS256.none, RS/ES (the algorithm-confusion attack), HS384/512 andcritheaders are rejected before the signature is checked.iss,aud,sub,jti,iatandexpare required,nbfis honoured, clock skew is limited, and signatures are compared in constant time. The secret must be at least 32 characters, and the service won't start in production withoutJWT_SECRET.JWT_SECRET_PREVIOUSallows key rotation.fid), and each rotation issues a new access token in that family.tokenAuthMiddleware, WebSocket upgrade auth and the refresh routes.tokenStrategynow takes user and tenant from the verified claims, not from headers the caller can set.Breaking change
Opaque
at_…access tokens are no longer accepted. Clients get JWTs from/auth/loginand/auth/refreshexactly as before; the responses now also includeaccessTokenExpiresAt.JWT_SECRETmust be set in production.Also fixed
middleware/logger.tshad a syntax error (??mixed with||without parentheses). It stopped every module that imports it,token-authincluded, from loading.Tests
main: no new failures in any suite that loaded onmain, and 2,262 passing vs 2,152.payouts.test.tsroute tests fail onmaintoo. A stale compilederrorHandler.jssits next toerrorHandler.ts, soinstanceof AppErrorfails. The new route tests use a duck-typed error handler to avoid this.auth.test.tsand thewebhookVerification*suites now load, but some of their tests fail on helpers upstream never exported (lookupApiKeyRecord,clearReplayCache,resetWebhookVerificationConfig).tsc: no new errors (one fewer thanmain).eslint: no new errors in changed files.New environment variables are documented in
ENV_VARS.md.Closes #1008
Closes #1011
Closes #1013
Closes #1014