108 lines
2.6 KiB
TypeScript
108 lines
2.6 KiB
TypeScript
/**
|
||
* 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));
|
||
|
||
/**
|
||
* 生成 CSP 指令字符串
|
||
*/
|
||
export function generateCSP(): string {
|
||
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 域名
|
||
"connect-src": [
|
||
"'self'",
|
||
process.env.NEXT_PUBLIC_API_URL || "",
|
||
// WebSocket 连接
|
||
"ws:",
|
||
"wss:",
|
||
].filter(Boolean),
|
||
|
||
// 媒体允许同源和 blob
|
||
"media-src": ["'self'", "blob:"],
|
||
|
||
// 对象不允许
|
||
"object-src": ["'none'"],
|
||
|
||
// 框架允许同源和指定父站
|
||
"frame-src": ["'self'", ...ALLOWED_PARENT_ORIGINS],
|
||
|
||
// 允许被嵌入到指定父站
|
||
"frame-ancestors": ["'self'", ...ALLOWED_PARENT_ORIGINS],
|
||
|
||
// 表单提交允许同源
|
||
"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 securityHeaders = [
|
||
{
|
||
key: "Content-Security-Policy",
|
||
value: generateCSP(),
|
||
},
|
||
{
|
||
key: "X-Content-Type-Options",
|
||
value: "nosniff",
|
||
},
|
||
{
|
||
key: "Referrer-Policy",
|
||
value: "strict-origin-when-cross-origin",
|
||
},
|
||
{
|
||
key: "Permissions-Policy",
|
||
value: "camera=(), microphone=(), geolocation=()",
|
||
},
|
||
];
|