修复/api/server-config暴露externalServerAuth的安全问题
This commit is contained in:
24
README.md
24
README.md
@@ -58,6 +58,7 @@
|
||||
- [配置文件](#配置文件)
|
||||
- [自动更新](#自动更新)
|
||||
- [环境变量](#环境变量)
|
||||
- [外部观影室服务器部署](#外部观影室服务器部署)
|
||||
- [弹幕后端部署](#弹幕后端部署)
|
||||
- [超分功能说明](#超分功能说明)
|
||||
- [AndroidTV 使用](#androidtv-使用)
|
||||
@@ -251,7 +252,10 @@ dockge/komodo 等 docker compose UI 也有自动更新功能
|
||||
| NEXT_PUBLIC_DANMAKU_CACHE_EXPIRE_MINUTES | 弹幕缓存失效时间(分钟数,设为 0 时不缓存) | 0 或正整数 | 4320(3天) |
|
||||
| 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. 重启应用即可使用外部观影室服务器
|
||||
|
||||
|
||||
|
||||
## 弹幕后端部署
|
||||
|
||||
@@ -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,返回默认配置
|
||||
|
||||
30
src/app/api/watch-room-auth/route.ts
Normal file
30
src/app/api/watch-room-auth/route.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ export interface WatchRoomConfig {
|
||||
enabled: boolean;
|
||||
serverType: 'internal' | 'external';
|
||||
externalServerUrl?: string;
|
||||
externalServerAuth?: string;
|
||||
externalServerAuth?: string; // 通过 /api/watch-room-auth 接口获取(需要登录)
|
||||
}
|
||||
|
||||
// LocalStorage 存储的房间信息
|
||||
|
||||
Reference in New Issue
Block a user