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
165 changes: 165 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ npm run build
# Run tests
npm test

# Run tests with coverage
npm test -- --coverage

# Start dev server (with hot reload)
npm run dev

Expand All @@ -40,6 +43,168 @@ npm start
| `npm test` | Run Jest tests |
| `npm run lint` | Run ESLint |

## Cache layer

The backend now includes a small in-memory cache layer for safe read-mostly contract lookups.

Cached flows:

- `GET /api/v1/contracts`
- `GET /api/v1/contracts/:id`

These cover the current read-heavy route and metadata lookup behavior in the repo.

### Strategy

- cache backend: in-memory
- default TTL: `30` seconds
- default max items: `100`
- invalidation: explicit invalidation of affected list/detail keys after `POST /api/v1/contracts`
- fallback: cache failures fall back to the source-of-truth service without failing the request

### Configuration

Environment variables:

- `CACHE_ENABLED` (default `true`)
- `CACHE_TTL_SECONDS` (default `30`)
- `CACHE_MAX_ITEMS` (default `100`)

### Security notes

- only shared read-mostly contract data is cached
- no auth/session/token data is cached
- no authorization results are cached
- cache keys are deterministic and normalized
- cache backend failures do not override source-of-truth correctness

### Threat scenarios validated

- stale read cache after writes
- cache backend read/write failure
- unbounded in-memory growth through item limit and TTL
- key collision risk reduced by explicit namespacing

## Integration tests

The backend includes a focused integration test suite covering representative API behavior for:

- `GET /health`
- `GET /api/v1/contracts`
- `GET /api/v1/contracts/:id`
- `POST /api/v1/contracts`

The tests verify:

- success paths
- malformed JSON handling
- validation failures
- duplicate creation conflicts
- not found and unsupported route behavior
- internal error sanitization
- cache hit/miss, invalidation, disabled-cache mode, and cache-failure fallback
- server bootstrap/start-stop behavior

### Test architecture

To keep tests deterministic and reviewer-friendly:

- `src/app.ts` creates the Express app without listening
- `src/index.ts` only starts the HTTP server
- `ContractService` uses per-app in-memory state for tests
- no external services or production systems are contacted

### Security and threat assumptions validated

- malformed input is rejected early
- invalid identifiers do not proceed to resource lookup
- duplicate resource creation is blocked
- self-dealing contract creation is denied
- internal errors are sanitized and do not leak stack traces
- integration tests do not depend on live external systems

### Documentation

Detailed backend test notes live in:

- `docs/backend/integration-testing.md`
- `docs/backend/cache-layer.md`
- `docs/backend/webhook-ingestion.md`

## Webhook ingestion

The backend now includes a signed blockchain webhook intake endpoint:

- `POST /webhooks/blockchain`

The endpoint verifies the webhook signature against the exact raw request body before parsing or processing the event.

### Signature and headers

By default the route expects:

- `X-Webhook-Signature`
- `X-Webhook-Timestamp`
- `X-Webhook-Id`

The signature is an HMAC-SHA256 digest of:

```text
<timestamp>.<raw-request-body>
```

The shared secret is loaded from `WEBHOOK_SECRET`.

### Supported events

The current supported normalized event types are:

- `contract.metadata.updated`
- `contract.payment.released`

Unknown event types are rejected safely.

### Replay and idempotency

The webhook layer currently protects against replay and duplicate delivery by:

- rejecting timestamps older than the configured max age
- reserving delivery ids during processing
- acknowledging already processed delivery ids without re-running the handler
- releasing failed deliveries so provider retries can succeed

### Configuration

Environment variables:

- `WEBHOOK_SECRET`
- `WEBHOOK_SIGNATURE_HEADER` default `x-webhook-signature`
- `WEBHOOK_TIMESTAMP_HEADER` default `x-webhook-timestamp`
- `WEBHOOK_EVENT_ID_HEADER` default `x-webhook-id`
- `WEBHOOK_MAX_AGE_SECONDS` default `300`
- `WEBHOOK_RAW_BODY_LIMIT` default `64kb`

### Security notes

- signature verification uses the raw request body, not reserialized JSON
- constant-time digest comparison is used
- unsigned, malformed, tampered, and stale requests are rejected before processing
- duplicate deliveries are handled idempotently
- oversized payloads are rejected with `413`
- responses do not expose secrets, signatures, or internal stack traces

### Test coverage

The webhook tests cover:

- valid signature acceptance
- missing, malformed, invalid, and stale signature rejection
- raw-body verification behavior
- duplicate delivery handling
- malformed JSON and unsupported events
- processing-failure sanitization
- missing configuration and oversized payload handling

## Contributing

1. Fork the repo and create a branch from `main`.
Expand Down
93 changes: 93 additions & 0 deletions docs/backend/cache-layer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Cache Layer

## Overview

The backend now uses a small in-memory cache layer for safe read-mostly contract reads.

Cached operations:

- `GET /api/v1/contracts`
- `GET /api/v1/contracts/:id`

These are the current best candidates for caching because they are deterministic, shared across callers, and derived from the same source-of-truth contract service.

## Architecture

The cache layer is introduced through:

- `src/cache/cache-store.ts`
- `src/cache/in-memory-cache.ts`
- `src/config/cache-config.ts`
- `src/services/cached-contract-service.ts`

`CachedContractService` wraps the existing `ContractService` and keeps cache concerns out of the route handlers.

## Strategy

- backend: in-memory cache
- key namespaces:
- `contracts:list`
- `contracts:detail:<normalized-id>`
- TTL: configurable, default 30 seconds
- max entries: configurable, default 100

## Invalidation

After `POST /api/v1/contracts` succeeds, the cache layer invalidates:

- the contracts list key
- the newly created contract detail key

This keeps stale list responses from being served after writes.

## Fallback behavior

Cache failures are treated as non-fatal:

- cache read failure -> fallback to source
- cache write failure -> still return source response
- cache delete failure -> write still succeeds

Correctness is prioritized over cache availability.

## Security assumptions and threat scenarios

Validated assumptions:

- only non-user-specific contract data is cached
- no credentials, sessions, or secrets are stored in cache
- no authorization decisions are cached
- cache keys use explicit namespaces and normalized identifiers

Threat scenarios considered:

- cross-user leakage: avoided by caching only shared public data
- cache poisoning via raw input: reduced by explicit key builders and route validation
- stale data after writes: mitigated by invalidation plus TTL
- cache outage: mitigated by fallback to source
- unbounded memory growth: reduced by TTL and max item eviction

## Testing

The test suite covers:

- cache hit/miss behavior
- TTL expiry
- max item eviction
- route-level cache behavior
- invalidation after writes
- disabled cache mode
- graceful fallback on cache failure

Run:

```bash
npm test
npm test -- --coverage
```

## Limitations

- The current backend is single-process and in-memory, so cache state is process-local.
- There is no distributed invalidation because the repo does not currently use Redis or another shared cache backend.
- If the API gains user-scoped/private read endpoints later, those should not be added to this cache without explicit scoping and security review.
67 changes: 67 additions & 0 deletions docs/backend/integration-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Backend Integration Testing

## Overview

The backend now includes a focused integration test suite for the API surface. The suite verifies representative success and failure behavior without relying on external services or mutable shared state.

## Test architecture

The production server bootstrap was split into two small pieces:

- `src/app.ts` creates an importable Express app
- `src/index.ts` starts the HTTP server

This keeps production behavior intact while allowing tests to instantiate the app directly without binding to a port.

## Isolation strategy

- Tests use an in-memory `ContractService`
- Each app instance gets its own service state
- No external database or network dependency is required
- No production services are called

## Covered API flows

- `GET /health`
- `GET /api/v1/contracts`
- `GET /api/v1/contracts/:id`
- `POST /api/v1/contracts`

## Covered failure paths

- malformed JSON
- missing required request fields
- invalid path identifiers
- duplicate resource creation
- unauthorized role/ownership style misuse via self-dealing protection
- unknown contract lookup
- unknown route
- unsupported method under current router behavior
- unexpected internal error sanitization

## Security assumptions validated

- malformed input is rejected
- invalid identifiers are rejected before lookup
- internal errors do not leak implementation details
- duplicate creation conflicts are handled consistently
- self-dealing contract creation is denied
- tests do not contact external systems

## How to run

```bash
npm test
npm run build
```

To inspect coverage:

```bash
npm test -- --coverage
```

## Limitations

- The backend currently has a small API surface and no persistent database integration yet, so tests focus on the present app behavior and in-memory service layer.
- Auth middleware does not exist in the current repo, so authentication-specific integration cases are not added beyond the present business-rule enforcement.
Loading
Loading