Files
lotteryFront/src/lib/csp-config.ts
kang 4fc2b38a40 refactor: 更新环境配置与 API 代理逻辑
- 修改 .env.example,优化玩家端配置说明,明确本地与线上环境的使用方式。
- 重构 API 代理逻辑,移除过时的开发代理文件,统一使用 Next.js 代理 API 请求。
- 更新 CSP 配置,简化连接来源说明,提升安全性。
- 调整 lottery-api-base.ts 中的注释,增强代码可读性。
2026-05-29 15:55:19 +08:00

138 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Content Security Policy (CSP) 配置
*
* 支持 iframe 嵌入场景,允许主站加载彩票系统
*/
// 允许的主站来源
const ALLOWED_PARENT_ORIGINS: string[] = [
process.env.NEXT_PUBLIC_MAIN_SITE_URL,
process.env.NEXT_PUBLIC_PARENT_ORIGIN,
// 开发环境
"http://localhost:5173",
"http://127.0.0.1:5173",
"http://192.168.0.101:5173",
"http://localhost:3801",
"http://127.0.0.1:3801",
"http://192.168.0.101:3801",
// 生产环境应从环境变量读取
].filter((o): o is string => Boolean(o));
function normalizeOrigin(value: string): string | null {
try {
return new URL(value).origin;
} catch {
return null;
}
}
export function staticAllowedParentOrigins(): string[] {
return Array.from(
new Set(
ALLOWED_PARENT_ORIGINS
.map((origin) => normalizeOrigin(origin))
.filter((origin): origin is string => origin !== null),
),
);
}
/**
* 生成 CSP 指令字符串
*/
export function generateCSP(extraParentOrigins: string[] = []): string {
const parentOrigins = Array.from(
new Set([
...staticAllowedParentOrigins(),
...extraParentOrigins
.map((origin) => normalizeOrigin(origin))
.filter((origin): origin is string => origin !== null),
]),
);
const directives: Record<string, string[]> = {
// 默认只允许同源
"default-src": ["'self'"],
// 脚本允许同源和内联Next.js 需要)
"script-src": ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
// 样式允许同源和内联
"style-src": ["'self'", "'unsafe-inline'"],
// 图片允许同源、data URL 和 blob
"img-src": ["'self'", "data:", "blob:"],
// 字体允许同源
"font-src": ["'self'"],
// 连接允许同源API 由 Next 代理到 LOTTERY_API_UPSTREAM
"connect-src": [
"'self'",
// WebSocket 连接
"ws:",
"wss:",
].filter(Boolean),
// 媒体允许同源和 blob
"media-src": ["'self'", "blob:"],
// 对象不允许
"object-src": ["'none'"],
// 框架允许同源和指定父站
"frame-src": ["'self'", ...parentOrigins],
// 允许被嵌入到指定父站
"frame-ancestors": ["'self'", ...parentOrigins],
// 表单提交允许同源
"form-action": ["'self'"],
};
// 构建 CSP 字符串
return Object.entries(directives)
.map(([key, values]) => {
if (values.length === 0) return key;
return `${key} ${values.join(" ")}`;
})
.join("; ");
}
/**
* 检测是否允许被 iframe 嵌入
* @param parentOrigin 父窗口来源
*/
export function isAllowedParent(parentOrigin: string): boolean {
if (ALLOWED_PARENT_ORIGINS.length === 0) return true; // 未配置时允许所有
return ALLOWED_PARENT_ORIGINS.some(
(origin) => origin && parentOrigin.startsWith(origin),
);
}
/**
* 安全头配置(用于 next.config.ts
*/
export const nonCspSecurityHeaders = [
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()",
},
];
export const securityHeaders = [
{
key: "Content-Security-Policy",
value: generateCSP(),
},
...nonCspSecurityHeaders,
];