Skip to content
Open
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
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
1 change: 1 addition & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npm test -- --passWithNoTests
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ It covers:
| deposit | `/api/deposit` | Bearer JWT |
| withdraw | `/api/withdraw` | Bearer JWT |
| vault | `/api/vault` | Bearer JWT |
| webhooks | `/api/webhooks` | Bearer JWT |
| admin | `/api/admin` | `X-Admin-Token` header |

### Viewing the docs locally
Expand Down
53 changes: 53 additions & 0 deletions docs/PR_GIT_HOOKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# chore: Git Hooks — Pre-commit Linting & Pre-push Tests

## Summary

Adds `husky` + `lint-staged` so formatting and lint errors are caught locally before they reach CI, and the test suite runs automatically before every push.

---

## What Changed

```
.husky/pre-commit ← runs lint-staged on staged files
.husky/pre-push ← runs full test suite before push
package.json ← lint-staged config + prepare script
package-lock.json ← updated lockfile
```

---

## Hook Behaviour

| Event | Command | Effect |
|---|---|---|
| `npm install` | `husky` (via `prepare`) | Hooks installed automatically for every contributor |
| `git commit` | `npx lint-staged` | ESLint auto-fix + Prettier format on staged `src/**/*.ts` and `tests/**/*.ts`; fixed files are re-staged |
| `git push` | `npm test -- --passWithNoTests` | Full Jest suite; push is blocked on test failures |

---

## lint-staged Config

```json
"lint-staged": {
"src/**/*.ts": ["eslint --fix", "prettier --write"],
"tests/**/*.ts": ["eslint --fix", "prettier --write"]
}
```

---

## Acceptance Criteria

- [x] `npm install` sets up hooks automatically via `prepare` script
- [x] Committing a file with a lint error is blocked (or auto-fixed and re-staged)
- [x] Committing a file with wrong formatting auto-fixes and re-stages it
- [x] CI still runs full lint + tests independently of hooks (unchanged)

---

## Notes

- Pre-existing test failures (missing `.env` vars, logic bugs in `client.test.ts`) are unrelated to this change — they fail identically on `main`. They will pass once a valid `.env` is in place.
- Use `git commit --no-verify` or `git push --no-verify` only when intentionally bypassing hooks (e.g. WIP commits, CI-only env setups).
134 changes: 134 additions & 0 deletions docs/PR_WEBHOOK_SYSTEM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# feat: Webhook Subscription System

## Summary

Adds a full server-push webhook system so integrators (mobile apps, dashboards) can receive real-time event notifications without polling.

---

## What Changed

### Database
| Model | Purpose |
|---|---|
| `WebhookSubscription` | Stores subscriber URL, event filters, HMAC secret, and active state per user |
| `WebhookDelivery` | Immutable delivery log — tracks attempts, HTTP status, and error per dispatch |

Migration: `prisma/migrations/20260627000000_add_webhook_tables/`

---

### API — `POST /api/webhooks` (new endpoint group)

All routes require `Authorization: Bearer <JWT>`.

| Method | Path | Description |
|---|---|---|
| `POST` | `/api/webhooks` | Create subscription — **returns secret once** |
| `GET` | `/api/webhooks` | List subscriptions (no secrets) |
| `GET` | `/api/webhooks/:id` | Get single subscription |
| `PATCH` | `/api/webhooks/:id` | Update URL / events / active state |
| `DELETE` | `/api/webhooks/:id` | Delete subscription + delivery history |

---

### Payload Signing

Every outbound webhook POST carries:

```
X-Neurowealth-Signature: sha256=<hmac-hex>
```

Computed as `HMAC-SHA256(secret, raw_body)`. Recipients verify by recomputing with their stored secret.

---

### Events Dispatched

| Event | Fired from |
|---|---|
| `transaction.confirmed` | `transaction-controller.ts` — on-chain tx confirmed |
| `deposit.received` | `stellar/events.ts` — deposit event processed |
| `withdraw.completed` | `stellar/events.ts` — withdraw event processed |
| `agent.rebalanced` | `stellar/events.ts` + `agent/loop.ts` — rebalance executed |

---

### Retry Logic

Failed deliveries are retried **up to 3 times** with exponential back-off:

```
attempt 1 → immediate
attempt 2 → wait 1 s
attempt 3 → wait 2 s
```

Each attempt is logged in `WebhookDelivery`. After all attempts fail the record is marked `FAILED` and logged as an error. Dispatch is always fire-and-forget — failures never block the request path.

---

## Files Changed

```
prisma/schema.prisma ← new models + relation
prisma/migrations/20260627000000_add_webhook_tables/migration.sql

src/utils/webhookSignature.ts ← generateSecret + signPayload
src/services/webhookDispatcher.ts ← dispatch + retry + delivery log
src/routes/webhooks.ts ← CRUD router
src/validators/webhook-validators.ts ← Zod schemas

src/index.ts ← mount /api/webhooks
src/stellar/events.ts ← fire deposit/withdraw/rebalance events
src/agent/loop.ts ← fire agent.rebalanced on rebalance
src/controllers/transaction-controller.ts ← fire transaction.confirmed

tests/unit/utils/webhookSignature.test.ts ← 5 tests
tests/unit/services/webhookDispatcher.test.ts ← 8 tests

docs/openapi.yaml ← webhooks tag + schemas + paths
README.md ← API table updated
```

---

## Tests

```
PASS tests/unit/utils/webhookSignature.test.ts (5 tests)
PASS tests/unit/services/webhookDispatcher.test.ts (8 tests)
```

Covers: secret uniqueness, HMAC correctness, success on first attempt, exhausted retry → FAILED, partial retry → SUCCESS, signature header format, subscription event filtering.

---

## Acceptance Criteria

- [x] `POST /api/webhooks` creates subscription, returns signing secret once
- [x] Payload signed with HMAC-SHA256, verifiable by recipient via `X-Neurowealth-Signature`
- [x] Failed deliveries retried (≤3) and logged in `WebhookDelivery` table
- [x] Unit tests for signature generation and retry logic
- [x] OpenAPI spec updated

---

## Testing Locally

```bash
# 1. Apply migration
npx prisma migrate dev

# 2. Create a subscription
curl -X POST http://localhost:3000/api/webhooks \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"url":"https://webhook.site/your-id","events":["deposit.received","transaction.confirmed"]}'
# → response includes `secret` — save it

# 3. Verify a delivery signature on receipt
echo -n '<raw_body>' | openssl dgst -sha256 -hmac '<secret>'
# should match X-Neurowealth-Signature header (minus "sha256=" prefix)
```
Loading