Skip to content
Merged
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
27 changes: 27 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
NODE_ENV=development
PORT=3000

CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001,http://localhost:5173

DATABASE_URL=postgresql://dev_user:dev_password@localhost:5432/neuro_backend_dev
DB_HOST=localhost
DB_PORT=5432
DB_USER=dev_user
DB_PASSWORD=dev_password
DB_NAME=neuro_backend_dev

JWT_SECRET=dev-jwt-secret-key
JWT_EXPIRY=24h
REFRESH_TOKEN_SECRET=dev-refresh-secret

ANTHROPIC_API_KEY=sk-dev-anthropic-key
OPENAI_API_KEY=sk-dev-openai-key

LOG_LEVEL=debug
LOG_FORMAT=json

SERVER_TIMEOUT=30000
MAX_REQUEST_SIZE=10mb

CORS_MAX_AGE=3600
CORS_ALLOW_CREDENTIALS=true
170 changes: 26 additions & 144 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,153 +1,35 @@
# Server
PORT=3001
# Application Environment
NODE_ENV=development
PORT=3000

# Stellar
STELLAR_NETWORK=testnet
STELLAR_RPC_URL=https://soroban-testnet.stellar.org
STELLAR_AGENT_SECRET_KEY=your_agent_stellar_secret_key_here
VAULT_CONTRACT_ID=your_deployed_contract_id_here
USDC_TOKEN_ADDRESS=testnet_usdc_contract_address_here

# AI
ANTHROPIC_API_KEY=get_from_console.anthropic.com
BRIAN_API_KEY=get_from_brianknows.org

# Database
DATABASE_URL=postgresql://postgres:password@localhost:5432/neurowealth
# Max connections Prisma opens per instance (applied as ?connection_limit=N).
# Size this against Postgres max_connections divided across all replicas.
DATABASE_CONNECTION_LIMIT=10
# How often (ms) to poll prisma.$metrics.json() and refresh the pool gauges on /metrics.
DB_POOL_METRICS_INTERVAL_MS=15000

# Wallet encryption
# Generate with: openssl rand -hex 32
WALLET_ENCRYPTION_KEY=generate_with_openssl_rand_hex_32

# WhatsApp (optional for local dev)
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
WHATSAPP_FROM=whatsapp:+14155238886
# CORS Configuration - Production Security
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001

# JWT
JWT_SEED=your_jwt_secret_seed_here
JWT_SESSION_TTL_HOURS=24
JWT_NONCE_TTL_MS=300000
JWT_CLEANUP_INTERVAL_MS=86400000

# Docker / Postgres (used by docker-compose.yml)
# Database name used by the Postgres container
DB_NAME=postgres
# Postgres password for the `postgres` user (set to a secure value in production)
# Database Configuration
DATABASE_URL=postgresql://user:password@localhost:5432/neuro_backend
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=password
DB_CONTAINER_NAME=neurowealth_db

# Security — Rate limiting
# Global limiter (public read APIs, portfolio, vault, etc.)
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX=100
# Auth endpoints — stricter to resist credential stuffing (15 min window, 20 req)
AUTH_RATE_LIMIT_WINDOW_MS=900000
AUTH_RATE_LIMIT_MAX=20
# Admin endpoints — tightest limits (15 min window, 10 req)
ADMIN_RATE_LIMIT_WINDOW_MS=900000
ADMIN_RATE_LIMIT_MAX=10
# Internal/agent service endpoints — higher throughput (1 min window, 500 req)
INTERNAL_RATE_LIMIT_WINDOW_MS=60000
INTERNAL_RATE_LIMIT_MAX=500
# Public webhooks — unauthenticated inbound callbacks (1 min window, 30 req)
WEBHOOK_RATE_LIMIT_WINDOW_MS=60000
WEBHOOK_RATE_LIMIT_MAX=30
# Trusted-IP bypass: comma-separated IPs that skip all rate limits
TRUSTED_IPS=
# Internal service token: value expected in X-Internal-Token request header
INTERNAL_SERVICE_TOKEN=

# Security — Reverse proxy
# Express trust proxy: hop count behind your load balancer (default 1).
# Use 0/false when the app is exposed directly; use 2 behind CDN + LB.
# Comma-separated keywords also supported: loopback,linklocal,uniquelocal
TRUST_PROXY=1

# HTTP Client (shared timeouts/retry/circuit-breaker for external services)
HTTP_CLIENT_TIMEOUT_MS=10000
HTTP_CLIENT_MAX_RETRIES=3
HTTP_CLIENT_BASE_DELAY_MS=200
HTTP_CLIENT_MAX_DELAY_MS=10000
HTTP_CLIENT_CIRCUIT_BREAKER_THRESHOLD=5
HTTP_CLIENT_CIRCUIT_BREAKER_RESET_MS=30000

# Global request timeout for general API routes (health/metrics and agent routes use tighter overrides)
REQUEST_TIMEOUT_MS=30000
DB_NAME=neuro_backend

# Dead Letter Queue
DLQ_ALERT_THRESHOLD=50
DLQ_ALERT_COOLDOWN_MS=900000
# Authentication
JWT_SECRET=your-secret-key-here
JWT_EXPIRY=24h
REFRESH_TOKEN_SECRET=refresh-secret-key

# External Alerting (optional)
# Slack webhook URL for DLQ alerts (e.g. https://hooks.slack.com/services/YOUR/WEBHOOK/URL)
SLACK_WEBHOOK_URL=
# API Keys
ANTHROPIC_API_KEY=your-anthropic-key-here
OPENAI_API_KEY=your-openai-key-here

# PagerDuty integration key for DLQ alerts
# Generate from: https://developer.pagerduty.com/
PAGERDUTY_ROUTING_KEY=
# Logging
LOG_LEVEL=info
LOG_FORMAT=json

# Admin dashboard URL for DLQ inspection links in alerts
ADMIN_DASHBOARD_URL=https://admin.neurowealth.io

# Graceful shutdown
# Grace period (ms) for in-flight requests to complete before force-exit
SHUTDOWN_DRAIN_TIMEOUT_MS=30000

# Data retention (all optional — defaults shown)
# Days to retain processed_events rows before deletion (default: 90)
RETENTION_PROCESSED_EVENTS_DAYS=90
# Days to retain RESOLVED dead_letter_events rows before deletion (default: 30)
RETENTION_DEAD_LETTER_EVENTS_DAYS=30
# Days to retain agent_logs rows before deletion (default: 60)
RETENTION_AGENT_LOGS_DAYS=60
# Interval between retention job runs in ms (default: 86400000 = 24 h)
RETENTION_INTERVAL_MS=86400000

# ── Internal Endpoints Authentication ──────────────────────────────────────────
#
# Protect /metrics and /api/agent/status from public access
#
# Choose ONE or more authentication methods:
# 1. X-Internal-Token header (for monitoring services)
# 2. IP whitelist (for internal Kubernetes/Docker networks)
# 3. ADMIN_API_TOKEN (existing admin bearer token)

# Internal service token for /metrics and /api/agent/status
# Used via header: X-Internal-Token: <value>
INTERNAL_SERVICE_TOKEN=your-secure-internal-token-here

# Comma-separated IP allowlist for internal endpoints
# Example: "127.0.0.1,10.0.0.0/8,172.17.0.0/16"
# Use for Kubernetes services, Docker internal networks
INTERNAL_IP_WHITELIST=127.0.0.1,::1

# Fraction of healthy (2xx, <1000ms) requests to log. Range: 0.0–1.0. Default: 0.1 (10%)
LOG_SAMPLE_RATE=0.1
# ── Secret Management (#261) ──────────────────────────────────────────────────
#
# SECRET_BACKEND controls where runtime secrets are loaded from at startup.
#
# Values:
# env — read directly from environment variables (default, no extra setup)
# aws-ssm — fetch from AWS SSM Parameter Store (requires @aws-sdk/client-ssm)
#
# When using aws-ssm the application needs an IAM role with ssm:GetParameter on
# the parameter path prefix defined by SSM_PREFIX. Store ONLY the IAM role ARN
# and SSM_PREFIX in your Kubernetes Secret/CI env — never the raw secret values.

SECRET_BACKEND=env

# SSM path prefix (only used when SECRET_BACKEND=aws-ssm).
# Parameters are expected at <SSM_PREFIX>/<SECRET_KEY>, e.g. /neurowealth/JWT_SEED
SSM_PREFIX=/neurowealth
# Server
SERVER_TIMEOUT=30000
MAX_REQUEST_SIZE=10mb

# IAM Role ARN for the EKS service account (IRSA) that has ssm:GetParameter
# Example: arn:aws:iam::123456789012:role/neurowealth-backend-ssm-reader
# AWS_ROLE_ARN=arn:aws:iam::ACCOUNT_ID:role/neurowealth-backend-ssm-reader
# CORS Specific
CORS_MAX_AGE=3600
CORS_ALLOW_CREDENTIALS=true
27 changes: 27 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
NODE_ENV=production
PORT=3000

CORS_ALLOWED_ORIGINS=https://app.neurowealth.io,https://admin.neurowealth.io

DATABASE_URL=postgresql://prod_user:prod_password@prod-db.example.com:5432/neuro_backend_prod
DB_HOST=prod-db.example.com
DB_PORT=5432
DB_USER=prod_user
DB_PASSWORD=prod_password
DB_NAME=neuro_backend_prod

JWT_SECRET=your-production-jwt-secret-key
JWT_EXPIRY=24h
REFRESH_TOKEN_SECRET=your-production-refresh-secret

ANTHROPIC_API_KEY=sk-your-prod-anthropic-key
OPENAI_API_KEY=sk-your-prod-openai-key

LOG_LEVEL=warn
LOG_FORMAT=json

SERVER_TIMEOUT=30000
MAX_REQUEST_SIZE=10mb

CORS_MAX_AGE=7200
CORS_ALLOW_CREDENTIALS=true
141 changes: 43 additions & 98 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,111 +1,56 @@
{
"name": "backend",
"name": "neuro-backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"description": "Neuro-Backend API with hardened CORS configuration",
"main": "src/app.ts",
"scripts": {
"dev": "nodemon",
"start": "node dist/src/app.js",
"dev": "nodemon --exec ts-node src/app.ts",
"build": "tsc",
"start": "node dist/index.js",
"smoke": "bash scripts/smoke-health.sh",
"smoke:health": "bash scripts/smoke-health.sh",
"wallet:rotate": "npx ts-node scripts/rotate-wallet-key.ts",
"wallet:rotate:dry-run": "npx ts-node scripts/rotate-wallet-key.ts --dry-run",
"lint": "npm run lint:types && npm run lint:style",
"lint:types": "tsc --noEmit",
"lint:style": "eslint \"src/**/*.ts\" \"prisma/**/*.ts\"",
"format": "prettier --write .github/workflows/node-ci.yml package.json .prettierrc.json eslint.config.mjs src/nlp/parser.ts src/stellar/dlq.ts src/whatsapp/handler.ts src/whatsapp/userManager.ts tests/unit/stellar/dlq-alerts.test.ts",
"format:check": "prettier --check .github/workflows/node-ci.yml package.json .prettierrc.json eslint.config.mjs src/nlp/parser.ts src/stellar/dlq.ts src/whatsapp/handler.ts src/whatsapp/userManager.ts tests/unit/stellar/dlq-alerts.test.ts",
"test": "jest",
"test:unit": "jest tests/unit",
"test:integration": "jest tests/integration",
"test:load:smoke": "k6 run tests/load/smoke.js",
"test:load": "k6 run tests/load/load.js",
"test:load:stress": "k6 run tests/load/stress.js",
"test:load:soak": "k6 run tests/load/soak.js",
"prisma:generate": "npx prisma generate"
"test:cors": "jest tests/cors.test.ts",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src tests",
"format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist coverage",
"prebuild": "npm run clean",
"prestart": "npm run build"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"roots": [
"<rootDir>/tests"
],
"moduleFileExtensions": [
"ts",
"js",
"json"
],
"transform": {
"^.+\\.ts$": [
"ts-jest",
{
"tsconfig": "tsconfig.json"
}
]
}
"keywords": [
"neuro",
"backend",
"cors",
"security",
"express",
"typescript"
],
"author": "Neurowealth",
"license": "MIT",
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
},
"prisma": {
"schema": "prisma/schema.prisma",
"seed": "ts-node prisma/seed.ts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Neurowealth/Backend.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/Neurowealth/Backend/issues"
},
"homepage": "https://github.com/Neurowealth/Backend#readme",
"dependencies": {
"@anthropic-ai/sdk": "^0.78.0",
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/auto-instrumentations-node": "^0.77.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
"@opentelemetry/sdk-node": "^0.219.0",
"@opentelemetry/sdk-trace-node": "^2.8.0",
"@prisma/client": "^5.22.0",
"@prisma/instrumentation": "^7.8.0",
"@sentry/node": "^10.62.0",
"@sentry/profiling-node": "^10.62.0",
"@stellar/stellar-sdk": "^14.5.0",
"bcryptjs": "^3.0.3",
"cors": "^2.8.6",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"express-rate-limit": "^8.2.1",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.3",
"node-cron": "^4.2.1",
"prom-client": "^15.1.3",
"twilio": "^4.11.0",
"winston": "^3.19.0",
"zod": "^4.3.6"
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/jest": "^30.0.0",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^25.3.0",
"@types/node-cron": "^3.0.11",
"@types/supertest": "^7.2.0",
"@types/twilio": "^3.19.3",
"@typescript-eslint/eslint-plugin": "^8.60.0",
"@typescript-eslint/parser": "^8.60.0",
"eslint": "^10.4.0",
"eslint-config-prettier": "^10.1.8",
"jest": "^30.2.0",
"nodemon": "^3.1.14",
"prettier": "^3.8.3",
"prisma": "^5.22.0",
"supertest": "^7.2.2",
"ts-jest": "^29.4.11",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.10",
"@types/node": "^20.10.6",
"@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.16.0",
"eslint": "^8.56.0",
"jest": "^29.7.0",
"nodemon": "^3.0.2",
"prettier": "^3.1.1",
"supertest": "^6.3.3",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.3"
"typescript": "^5.3.3"
}
}
Loading
Loading