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:
@@ -13,17 +13,21 @@ import { JwtAuthGuard, PermissionsGuard } from './guards';
|
||||
imports: [
|
||||
PrismaModule,
|
||||
PassportModule,
|
||||
JwtModule.register({
|
||||
// envOr, not `??`: docker-compose passes every optional variable as
|
||||
// `${X:-}`, so an unconfigured one arrives as "" rather than undefined.
|
||||
// An empty expiresIn makes jsonwebtoken throw on EVERY sign, which 500s
|
||||
// admin login in Docker only. See DEPLOY-02.
|
||||
secret: envOr('JWT_SECRET', 'dev-only-insecure-secret'),
|
||||
// jsonwebtoken types expiresIn as a template-literal union, which a plain
|
||||
// `string` from process.env does not satisfy; the cast is the documented escape.
|
||||
signOptions: {
|
||||
expiresIn: envOr('JWT_EXPIRES_IN', '12h') as `${number}h`,
|
||||
},
|
||||
// registerAsync: read when the app is created, not when this file is
|
||||
// imported, so a JWT_SECRET filled in at boot (boot-secrets.ts) is used.
|
||||
JwtModule.registerAsync({
|
||||
useFactory: () => ({
|
||||
// envOr, not `??`: docker-compose passes every optional variable as
|
||||
// `${X:-}`, so an unconfigured one arrives as "" rather than undefined.
|
||||
// An empty expiresIn makes jsonwebtoken throw on EVERY sign, which 500s
|
||||
// admin login in Docker only. See DEPLOY-02.
|
||||
secret: envOr('JWT_SECRET', 'dev-only-insecure-secret'),
|
||||
// jsonwebtoken types expiresIn as a template-literal union, which a plain
|
||||
// `string` from process.env does not satisfy; the cast is the documented escape.
|
||||
signOptions: {
|
||||
expiresIn: envOr('JWT_EXPIRES_IN', '12h') as `${number}h`,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user