Skip to content

Commit 4f3a8f6

Browse files
committed
security: harden production defaults and shared throttling
1 parent bc7606a commit 4f3a8f6

23 files changed

Lines changed: 781 additions & 43 deletions

.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ ADMIN_PASSWORD=admin123
2525
# Steam polling
2626
STEAM_POLL_INTERVAL_SEC=5
2727

28+
# Shared rate limiting (optional outside Docker; Docker Compose defaults to redis://redis:6379/0)
29+
RATE_LIMIT_REDIS_URL=
30+
# Leave empty for the default prefix derived from NODE_ENV, or override per environment/shared Redis.
31+
RATE_LIMIT_REDIS_PREFIX=
32+
33+
# Production transport security
34+
FORCE_HTTPS=true
35+
# OPENAPI_ENABLED defaults to true in dev/test and false in production.
36+
# Set to true only when you explicitly want to expose /api-docs/openapi.json.
37+
2838
# Cloudflare Turnstile (optional anti-bot registration protection)
2939
TURNSTILE_ENABLED=false
3040
TURNSTILE_SITE_KEY=

.github/workflows/ci.yml

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
name: CI
22

3+
permissions:
4+
contents: read
5+
36
on:
47
push:
58
branches: ['**']
69
pull_request:
710

811
jobs:
12+
secrets:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
- uses: gitleaks/gitleaks-action@v2
19+
920
backend:
1021
runs-on: ubuntu-latest
1122
defaults:
@@ -18,10 +29,25 @@ jobs:
1829
node-version: 20
1930
cache: npm
2031
cache-dependency-path: backend/package-lock.json
21-
- run: npm install
32+
- run: npm ci
2233
- run: npm run lint
2334
- run: npm run test
2435

36+
backend-audit:
37+
runs-on: ubuntu-latest
38+
defaults:
39+
run:
40+
working-directory: backend
41+
steps:
42+
- uses: actions/checkout@v4
43+
- uses: actions/setup-node@v4
44+
with:
45+
node-version: 20
46+
cache: npm
47+
cache-dependency-path: backend/package-lock.json
48+
- run: npm ci
49+
- run: npm audit --omit=dev --audit-level=high
50+
2551
frontend:
2652
runs-on: ubuntu-latest
2753
defaults:
@@ -34,6 +60,21 @@ jobs:
3460
node-version: 20
3561
cache: npm
3662
cache-dependency-path: frontend/package-lock.json
37-
- run: npm install
63+
- run: npm ci
3864
- run: npm run lint
3965
- run: npm run test
66+
67+
frontend-audit:
68+
runs-on: ubuntu-latest
69+
defaults:
70+
run:
71+
working-directory: frontend
72+
steps:
73+
- uses: actions/checkout@v4
74+
- uses: actions/setup-node@v4
75+
with:
76+
node-version: 20
77+
cache: npm
78+
cache-dependency-path: frontend/package-lock.json
79+
- run: npm ci
80+
- run: npm audit --omit=dev --audit-level=high

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Open-source web implementation of Steam Desktop Authenticator (SDA) with multi-u
1313
- **Fastify (instead of Express)**: lower overhead and better throughput for polling/real-time workloads.
1414
- **TypeScript on backend/frontend**: safer refactoring and better API contracts.
1515
- **MySQL 8 + Docker internal network**: relational consistency, easy VPS deploy, DB isolated from public access.
16+
- **Redis-backed rate limiting in Docker**: shared throttling across backend instances and backend restarts, with in-memory fallback outside Redis setups.
1617
- **React + Vite + Tailwind**: fast DX + responsive UI + simple theming.
1718
- **Vite PWA plugin**: installable app + service worker caching.
1819
- **Aiogram bot**: mature async Telegram framework for command and deep-link flows.
@@ -33,7 +34,7 @@ Open-source web implementation of Steam Desktop Authenticator (SDA) with multi-u
3334
- Password re-confirmation for sensitive actions such as `.maFile` export, recovery code reveal and manual Steam session save.
3435
- i18n EN/RU + light/dark theme.
3536
- Admin panel with global registration toggle and user deletion.
36-
- OpenAPI docs JSON at `/api-docs/openapi.json`.
37+
- OpenAPI docs JSON at `/api-docs/openapi.json` when `OPENAPI_ENABLED=true`.
3738

3839
## Default admin
3940

@@ -45,6 +46,7 @@ Open-source web implementation of Steam Desktop Authenticator (SDA) with multi-u
4546
Important:
4647
- The default admin password is for local bootstrap only.
4748
- Production startup is blocked if `ADMIN_PASSWORD=admin123`.
49+
- Production startup is also blocked if `JWT_SECRET`, `COOKIE_SECRET`, or `ENCRYPTION_KEY` still use placeholder-style values such as `change_me...`, are shorter than 32 characters, or reuse the same secret value.
4850

4951
## Architecture
5052

@@ -81,7 +83,7 @@ make dev
8183
Open:
8284
- Frontend: `http://localhost:3000`
8385
- Backend API: `http://localhost:3001`
84-
- OpenAPI: `http://localhost:3001/api-docs/openapi.json`
86+
- OpenAPI: `http://localhost:3001/api-docs/openapi.json` (enabled by default in dev/test)
8587

8688
## Make targets
8789

@@ -102,12 +104,32 @@ Core variables:
102104
- `DB_*` MySQL connection and bootstrap user credentials.
103105
- `JWT_SECRET`, `COOKIE_SECRET`, `ENCRYPTION_KEY` security secrets.
104106
- `APP_URL`, `API_URL` frontend/backend origins.
107+
- `RATE_LIMIT_REDIS_URL`, `RATE_LIMIT_REDIS_PREFIX` shared rate limiting backend; Docker Compose defaults to internal Redis and a NODE_ENV-based prefix.
108+
- `FORCE_HTTPS` enables HTTP->HTTPS redirects in production (`true` by default, can be disabled for special deployments).
109+
- `OPENAPI_ENABLED` controls `/api-docs/openapi.json`; enabled by default in dev/test and should stay off in production unless explicitly needed.
105110
- `TELEGRAM_BOT_TOKEN`, `TELEGRAM_BOT_USERNAME` bot settings.
106111
- `STEAM_POLL_INTERVAL_SEC` auto-confirm polling interval.
107112
- `TURNSTILE_ENABLED`, `TURNSTILE_SITE_KEY`, `TURNSTILE_SECRET_KEY` optional Cloudflare Turnstile backend protection.
108113
- `VITE_TURNSTILE_SITE_KEY` frontend public site key for invisible Turnstile registration flow. Use the same value as `TURNSTILE_SITE_KEY` because it is the same public Cloudflare site key, just exposed to the Vite frontend build via the `VITE_` prefix.
109114

115+
Generate strong secrets before production deploy, for example:
116+
117+
```bash
118+
openssl rand -hex 32
119+
openssl rand -hex 32
120+
openssl rand -hex 32
121+
```
122+
123+
Then place the generated values into `.env` as:
124+
125+
```env
126+
JWT_SECRET=<first generated value>
127+
COOKIE_SECRET=<second generated value>
128+
ENCRYPTION_KEY=<third generated value>
129+
```
130+
110131
If `TELEGRAM_BOT_TOKEN` is empty or starts with `change_me`, bot service stays in disabled idle mode (no crash, API stays up).
132+
If you enable `OPENAPI_ENABLED` in production, prefer exposing it only behind admin auth, VPN, or IP allowlisting.
111133

112134
## Security model
113135

@@ -117,9 +139,11 @@ If `TELEGRAM_BOT_TOKEN` is empty or starts with `change_me`, bot service stays i
117139
- **Sensitive actions**: password re-confirmation required before `.maFile` export, recovery code reveal and manual Steam session updates.
118140
- **CSRF**: double-submit protection for mutating endpoints.
119141
- **Brute-force/DoS**: `rate-limiter-flexible` in auth/write paths.
142+
- **Shared throttling**: Docker deployments use Redis-backed rate limits with in-memory insurance fallback if Redis is temporarily unavailable; current Compose defaults do not persist limiter state across Redis restarts.
120143
- **Anti-bot registration**: signed registration challenge, honeypot, dedicated registration rate limiter and optional invisible Cloudflare Turnstile.
121144
- **Hardening**: `helmet`, CORS with credentials.
122145
- **WebSocket auth**: cookie/bearer only, query-string auth disabled.
146+
- **API caching**: `/api/*` responses are served with `Cache-Control: no-store` to reduce leakage of session-bound data.
123147
- **DB isolation**: MySQL only on internal Docker network (`db_internal`).
124148

125149
## Telegram flows
@@ -135,6 +159,8 @@ If `TELEGRAM_BOT_TOKEN` is empty or starts with `change_me`, bot service stays i
135159
3. Bot confirms code.
136160
4. Web page polls and creates session automatically.
137161

162+
Deploy frontend and backend together when changing Telegram login polling, because the flow now requires the `x-telegram-poll-token` header instead of the legacy query-string token.
163+
138164
## Steam confirmations notes
139165

140166
Steam mobile confirmations require valid session tokens (`steamLoginSecure`, `sessionid`, optional `oauthToken`).

backend/package-lock.json

Lines changed: 79 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"dotenv": "^16.4.5",
2626
"fastify": "^5.7.4",
2727
"fastify-plugin": "^5.1.0",
28+
"ioredis": "^5.10.1",
2829
"jsonwebtoken": "^9.0.2",
2930
"mysql2": "^3.12.0",
3031
"pino-pretty": "^13.0.0",

backend/src/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import botRoutes from './routes/bot';
1313
import notificationRoutes from './routes/notifications';
1414
import { wsHub } from './services/wsHub';
1515
import { getBearerToken, verifySessionToken } from './utils/jwt';
16+
import { isProd } from './config/env';
1617

1718
function cookieValue(cookieHeader: string | undefined, name: string): string | null {
1819
if (!cookieHeader) {
@@ -33,6 +34,7 @@ function cookieValue(cookieHeader: string | undefined, name: string): string | n
3334

3435
export async function buildApp() {
3536
const app = Fastify({
37+
trustProxy: isProd ? 'loopback, linklocal, uniquelocal' : false,
3638
logger: {
3739
transport:
3840
process.env.NODE_ENV === 'development'

0 commit comments

Comments
 (0)