fix(api): start in production with safe fallbacks instead of crash-looping (502)

A production API with incomplete config threw before listening, so the
container restarted forever and every /api request through nginx was a 502.
The problems are now printed as one loud warning and each gets a safe
fallback:

- JWT_SECRET / JWT_CUSTOMER_SECRET missing, weak or equal: a random key is
  generated once and kept in the Setting table (boot-secrets.ts), so tokens
  survive restarts. A strong value in the environment always wins.
  JwtModule now uses registerAsync so it reads the key after boot fills it.
- OTP_MASTER_CODE: ignored in production unless
  TEST_SERVER_ALLOW_MASTER_CODE=yes.
- CORS_ORIGINS: invalid entries dropped; empty allows every origin.

Tests: boot-secrets.spec, production-checks.spec. 118 tests pass.

سرور با تنظیمات ناقص دیگر مدام ریست نمی‌شود و ورود دوباره کار می‌کند.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
s.esmaeili
2026-10-10 16:19:30 +03:30
co-authored by Cursor
parent 4336ec4a62
commit c34bf80e6f
9 changed files with 359 additions and 94 deletions
@@ -13,6 +13,7 @@ import {
import { AppSessionService } from '../sessions/app-session.service';
import { OTP_ONLY_PASSWORD_HASH } from '../auth/password-hash';
import { envList, envOr, envOrNull, pickExactUri } from '../../common/env';
import { masterCodeAllowed } from '../../common/production-checks';
import { CUSTOMER_TOKEN_TYPE, EXPERT_TOKEN_TYPE, type CustomerJwtPayload } from './customer-jwt.strategy';
import { faToEn } from './digits';
import { SmsService, smsConfigured } from '../sms/sms.service';
@@ -47,6 +48,7 @@ const RESEND_COOLDOWN_MS = 60 * 1000;
* the environment when this module was first imported.
*/
export function masterOtpCode(): string | null {
if (!masterCodeAllowed(process.env)) return null;
const raw = process.env.OTP_MASTER_CODE?.trim();
return raw ? raw : null;
}