修复/api/server-config暴露externalServerAuth的安全问题

This commit is contained in:
mtvpls
2025-12-26 22:13:13 +08:00
parent 3050cd48a9
commit fb9f55136d
5 changed files with 76 additions and 4 deletions

View File

@@ -58,6 +58,7 @@
- [配置文件](#配置文件)
- [自动更新](#自动更新)
- [环境变量](#环境变量)
- [外部观影室服务器部署](#外部观影室服务器部署)
- [弹幕后端部署](#弹幕后端部署)
- [超分功能说明](#超分功能说明)
- [AndroidTV 使用](#androidtv-使用)
@@ -251,7 +252,10 @@ dockge/komodo 等 docker compose UI 也有自动更新功能
| NEXT_PUBLIC_DANMAKU_CACHE_EXPIRE_MINUTES | 弹幕缓存失效时间(分钟数,设为 0 时不缓存) | 0 或正整数 | 43203天 |
| ENABLE_TVBOX_SUBSCRIBE | 是否启用 TVBOX 订阅功能 | true/false | false |
| TVBOX_SUBSCRIBE_TOKEN | TVBOX 订阅 API 访问 Token如启用TVBOX功能必须设置该项 | 任意字符串 | (空) |
| WATCH_ROOM_ENABLED | 是否启用观影室功能vercel部署不支持该功能后续可能会开发外部服务器) | true/false | false |
| WATCH_ROOM_ENABLED | 是否启用观影室功能vercel部署不支持该功能可使用外部服务器) | true/false | false |
| WATCH_ROOM_SERVER_TYPE | 观影室服务器类型 | internal/external | internal |
| WATCH_ROOM_EXTERNAL_SERVER_URL | 外部观影室服务器地址(当 SERVER_TYPE 为 external 时必填) | WebSocket URL | (空) |
| WATCH_ROOM_EXTERNAL_SERVER_AUTH | 外部观影室服务器认证令牌(当 SERVER_TYPE 为 external 时必填) | 任意字符串 | (空) |
| NEXT_PUBLIC_VOICE_CHAT_STRATEGY | 观影室语音聊天策略 | webrtc-fallback/server-only | webrtc-fallback |
| NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD | 是否启用服务器离线下载功能(开启后也仅管理员和站长可用) | true/false | false |
| OFFLINE_DOWNLOAD_DIR | 离线下载文件存储目录 | 任意有效路径 | /data |
@@ -279,6 +283,24 @@ NEXT_PUBLIC_VOICE_CHAT_STRATEGY 选项解释:
- webrtc-fallback使用 WebRTC P2P 连接,失败时自动回退到服务器中转(推荐)
- server-only仅使用服务器中转适用于无法建立 P2P 连接的网络环境)
### 外部观影室服务器部署
如果您在 Vercel 等无法运行 WebSocket 服务器的平台部署,或希望将观影室服务器独立部署,可以使用外部观影室服务器。
推荐使用由 [tgs9915](https://github.com/tgs9915) 开发的 [watch-room-server](https://github.com/tgs9915/watch-room-server) 项目进行部署。
**配置步骤:**
1. 按照 [watch-room-server](https://github.com/tgs9915/watch-room-server) 的文档部署外部服务器
2. 在 MoonTVPlus 中设置以下环境变量:
```env
WATCH_ROOM_ENABLED=true
WATCH_ROOM_SERVER_TYPE=external
WATCH_ROOM_EXTERNAL_SERVER_URL=wss://your-watch-room-server.com
WATCH_ROOM_EXTERNAL_SERVER_AUTH=your_secure_token
```
3. 重启应用即可使用外部观影室服务器
## 弹幕后端部署

View File

@@ -13,11 +13,12 @@ export async function GET(request: NextRequest) {
const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
// 观影室配置从环境变量读取
// 注意:不要暴露 externalServerAuth 到前端,这是敏感凭据
const watchRoomConfig = {
enabled: process.env.WATCH_ROOM_ENABLED === 'true',
serverType: (process.env.WATCH_ROOM_SERVER_TYPE as 'internal' | 'external') || 'internal',
externalServerUrl: process.env.WATCH_ROOM_EXTERNAL_SERVER_URL,
externalServerAuth: process.env.WATCH_ROOM_EXTERNAL_SERVER_AUTH,
// externalServerAuth 不应该暴露给前端
};
// 如果使用 localStorage返回默认配置

View File

@@ -0,0 +1,30 @@
/* eslint-disable no-console */
import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
export const runtime = 'nodejs';
/**
* GET /api/watch-room-auth
*
* 需要登录才能访问的接口,返回观影室外部服务器的认证信息
* 这样可以避免将敏感的 externalServerAuth 暴露给未登录用户
*/
export async function GET(request: NextRequest) {
console.log('watch-room-auth called: ', request.url);
// 从 cookie 获取用户信息
const authInfo = getAuthInfoFromCookie(request);
if (!authInfo || !authInfo.username) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// 返回外部服务器认证信息
const externalServerAuth = process.env.WATCH_ROOM_EXTERNAL_SERVER_AUTH;
return NextResponse.json({
externalServerAuth: externalServerAuth || null,
});
}

View File

@@ -157,8 +157,27 @@ export function WatchRoomProvider({ children }: WatchRoomProviderProps) {
enabled: data.WatchRoom?.enabled ?? false, // 默认不启用
serverType: data.WatchRoom?.serverType ?? 'internal',
externalServerUrl: data.WatchRoom?.externalServerUrl,
externalServerAuth: data.WatchRoom?.externalServerAuth,
};
// 如果使用外部服务器,需要获取认证信息(需要登录)
if (watchRoomConfig.serverType === 'external' && watchRoomConfig.enabled) {
try {
const authResponse = await fetch('/api/watch-room-auth');
if (authResponse.ok) {
const authData = await authResponse.json();
watchRoomConfig.externalServerAuth = authData.externalServerAuth;
} else {
console.error('[WatchRoom] Failed to load auth info:', authResponse.status);
// 如果无法获取认证信息,禁用观影室
watchRoomConfig.enabled = false;
}
} catch (error) {
console.error('[WatchRoom] Error loading auth info:', error);
// 如果无法获取认证信息,禁用观影室
watchRoomConfig.enabled = false;
}
}
setConfig(watchRoomConfig);
setIsEnabled(watchRoomConfig.enabled);

View File

@@ -129,7 +129,7 @@ export interface WatchRoomConfig {
enabled: boolean;
serverType: 'internal' | 'external';
externalServerUrl?: string;
externalServerAuth?: string;
externalServerAuth?: string; // 通过 /api/watch-room-auth 接口获取(需要登录)
}
// LocalStorage 存储的房间信息