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
23 changes: 15 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,25 @@ STEEM_RPC_URL=https://api.steemit.com
# CONVEYOR_USERNAME=steem
# CONVEYOR_POSTING_WIF=5J...

# Session and CSRF secrets (generate strong values in production)
# SESSION_SECRET=your-session-secret-here
CSRF_SECRET=your-csrf-secret-change-in-production
# CSRF secret — REQUIRED in production. Without it, all mutations (login,
# broadcast, recovery) are rejected. Generate a strong random value, e.g.:
# openssl rand -hex 32
# SESSION_SECRET is declared for reference but is NOT used by this app (there
# is no server-side session); do not rely on it.
# CSRF_SECRET=please-generate-a-strong-random-secret

# Optional: Mixpanel analytics (client-side token)
# NEXT_PUBLIC_MIXPANEL_TOKEN=your-mixpanel-token

# Optional: Rate limiting (currently in-memory; config in code)
# RATE_LIMIT_ENABLED=true
# RATE_LIMIT_WINDOW=60
# RATE_LIMIT_MAX_QUERY=100
# RATE_LIMIT_MAX_BROADCAST=10
# Optional: Rate limiting (Redis-backed when REDIS_URL is set; per-process
# in-memory fallback otherwise — not shared across instances).
# RATE_LIMIT_ALLOW_MEMORY_FALLBACK=false # reject (503) instead of falling back

# Number of trusted reverse-proxy hops in front of this app (ELB/OpenResty).
# Set this so rate limiting keys on the real client IP rather than a spoofable
# X-Forwarded-For. e.g. behind a single ELB/OpenResty layer use 1.
# When unset, the limiter falls back to a single shared 'unknown' bucket.
# TRUST_PROXY_COUNT=1

# Redis (required for Phase 2: shared cache, rate limiting, auth challenges)
# Production: AWS ElastiCache endpoint
Expand Down
8 changes: 5 additions & 3 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ services:
- STEEM_RPC_URL=${STEEM_RPC_URL:-https://api.steemit.com}
- STEEM_RPC_FALLBACKS=${STEEM_RPC_FALLBACKS:-}

# Security (IMPORTANT: Override in production!)
- SESSION_SECRET=${SESSION_SECRET:-change-me-in-production}
- CSRF_SECRET=${CSRF_SECRET:-change-me-in-production}
# Security — CSRF_SECRET is REQUIRED in production. With no secret all
# mutations are rejected. Generate one: openssl rand -hex 32
# SESSION_SECRET is unused (no server-side session) and kept for reference.
- SESSION_SECRET=${SESSION_SECRET:-}
- CSRF_SECRET=${CSRF_SECRET:?CSRF_SECRET is required in production}
- SESSION_MAX_AGE=${SESSION_MAX_AGE:-604800}

# Analytics
Expand Down
12 changes: 11 additions & 1 deletion drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { defineConfig } from 'drizzle-kit';

// DATABASE_URL is required. There is intentionally no hardcoded fallback with
// real credentials — a local dev value should be set via .env or the shell,
// e.g. DATABASE_URL=mysql://root:root@127.0.0.1/wallet_dev
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error(
'drizzle.config: DATABASE_URL is not set. Provide it via env (e.g. .env).'
);
}

export default defineConfig({
dialect: 'mysql',
schema: './src/lib/db/schema/index.ts',
out: './drizzle',
dbCredentials: {
url: process.env.DATABASE_URL ?? 'mysql://root:12345678@127.0.0.1/wallet_dev',
url: databaseUrl,
},
});
28 changes: 28 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,34 @@ const nextConfig: NextConfig = {
{ source: '/~witnesses', destination: '/witnesses', permanent: true },
];
},

// Security response headers applied to all routes. A full script-src CSP is
// intentionally not added here because Next.js relies on inline runtime for
// hydration; instead we set frame-ancestors (clickjacking) plus the standard
// hardening headers. Strengthen to a strict script-src once nonce support is
// wired up.
async headers() {
return [
{
source: '/:path*',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
// Mitigate clickjacking; allow no framing.
{ key: 'Content-Security-Policy', value: "frame-ancestors 'none'" },
],
},
];
},
};

export default withNextIntl(nextConfig);
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
},
"dependencies": {
"@reduxjs/toolkit": "^2.11.2",
"@steemit/steem-js": "^1.0.19",
"@steemit/steem-js": "^1.0.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"drizzle-orm": "^0.45.2",
Expand All @@ -36,6 +36,7 @@
"react-redux": "^9.2.0",
"recharts": "^3.8.1",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.1",
"shadcn": "^4.3.1",
"sonner": "^2.0.7",
Expand Down
Loading
Loading