// 生成并持久化随机 JWT_SECRET(必须在路由模块加载之前执行,middleware/auth.js 模块加载时即读取 SECRET)
try {
const fs = require('fs');
const crypto = require('crypto');
const cfgPath = './.env.json';
let cfg = {};
if (fs.existsSync(cfgPath)) {
cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
}
if (!cfg.jwt_secret) {
cfg.jwt_secret = crypto.randomBytes(48).toString('hex');
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
}
process.env.JWT_SECRET = cfg.jwt_secret;
// RainID OIDC 机密 client_secret(仅存 .env.json,gitignored;lib/rainid.js 读取)
if (cfg.rainid_client_secret) process.env.RAINID_CLIENT_SECRET = cfg.rainid_client_secret;
} catch {}
const express = require('express');
const cors = require('cors');
const path = require('path');
const { getDb } = require('./db');
const authRoutes = require('./routes/auth');
const adminLinkRoutes = require('./routes/admin-links');
const announcementRoutes = require('./routes/announcements');
const forumRoutes = require('./routes/forum');
const blogRoutes = require('./routes/blog');
const passwordRoutes = require('./routes/passwords');
const settingsRoutes = require('./routes/settings');
const emailRoutes = require('./routes/email');
const profileRoutes = require('./routes/profile');
const userRoutes = require('./routes/users');
const captchaRoutes = require('./routes/captcha');
const uploadRoutes = require('./routes/upload');
const setupRoutes = require('./routes/setup');
const proxyRoutes = require('./routes/proxy');
const importRoutes = require('./routes/import');
const noteRoutes = require('./routes/notes');
const feedRoutes = require('./routes/feed');
const terminalRoutes = require('./routes/terminal');
const ticketRoutes = require('./routes/tickets');
const oidcRoutes = require('./routes/oidc');
const { blogSSR, forumSSR, categorySSR, sitemapXml } = require('./ssr');
const app = express();
// M4:反向代理 IP 信任——仅当环境变量 TRUST_PROXY=1 时信任一层 X-Forwarded-For
//(与 routes/terminal.js getClientIp 同语义,统一开关)。限流桶(express-rate-limit)
// 按 req.ip 计数:Cloudflare/nginx 场景必须开启,否则全站共用一个桶可被刷满 DoS,
// 且直连源站可无限绕过限流。
// 权衡:开启后 req.ip 取自 X-Forwarded-For,客户端可伪造该头(除非反代覆盖/剥离),
// 因此仅应在受控反代之后启用;直连部署保持关闭(false)。
app.set('trust proxy', process.env.TRUST_PROXY === '1' ? 1 : false);
// L1:全站安全响应头中间件(须在路由之前注册,全局生效)
// 豁免:/api/proxy/fetch 与 /proxy/* 前缀代理不设置 X-Frame-Options 与 CSP——代理响应自行管理头
//(routes/proxy.js 已剥离上游 X-Frame-Options/CSP 并注入
您即将离开 ${escapeHtml(siteName)} 前往外部链接,本站不对其内容的真实性、安全性与可用性负责,请谨慎访问。
${escapedUrl}将在 5 秒后自动跳转