diff --git a/.env.example b/.env.example index e14312f..453ab33 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,9 @@ MONGODB_URI=mongodb://127.0.0.1:27017 MONGODB_DB=torrent_console +# 站点访问密码(留空则站点完全开放;设置后进入站点需在 /gate 输入此密码,cookie 由服务端校验) +WEBPASSWORD= + # 访问搜索源时使用的 HTTP 代理(国内网络连不上公开磁力站时配这里) # 例如:http://127.0.0.1:7890 SEARCH_PROXY= diff --git a/app/layouts/default.vue b/app/layouts/default.vue index c1ca116..a5dfeff 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -45,7 +45,7 @@ const mongoPill = computed(() => { {{ health?.webTorrent ? '磁力解析就绪' : '磁力解析不可用' }} - + {{ mongoPill.text }} diff --git a/app/pages/gate.vue b/app/pages/gate.vue new file mode 100644 index 0000000..c011ac2 --- /dev/null +++ b/app/pages/gate.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/app/pages/settings.vue b/app/pages/settings.vue index 7ad0710..6feb93b 100644 --- a/app/pages/settings.vue +++ b/app/pages/settings.vue @@ -321,7 +321,6 @@ onMounted(load)
MongoDB
{{ health?.mongo.ok ? '已连接' : '未连接' }} -
{{ health.mongo.error }}
磁力解析
diff --git a/app/types/api.ts b/app/types/api.ts index 957224f..0675405 100644 --- a/app/types/api.ts +++ b/app/types/api.ts @@ -95,7 +95,7 @@ export interface ClientsResponse { export interface HealthResponse { ok: boolean time: string - mongo: { ok: boolean; error: string | null } + mongo: { ok: boolean } webTorrent: boolean providers: { id: string; label: string; homepage: string }[] enabledProviders: string[] diff --git a/app/utils/i18n-dict.ts b/app/utils/i18n-dict.ts index 2347351..4ec17f8 100644 --- a/app/utils/i18n-dict.ts +++ b/app/utils/i18n-dict.ts @@ -225,7 +225,17 @@ export const zh2en: Record = { '点击关键词直接搜索': 'Click a keyword to search', '已写入 MongoDB': 'Saved to MongoDB', 'MongoDB 未连接,未记录': 'MongoDB not connected, not recorded', - 'MongoDB 未连接,搜索记录与种子缓存不会被保存(其余功能可用)。在 .env 配置正确的 MONGODB_URI 后刷新即可。': 'MongoDB not connected; search history and torrent cache will not be saved (other features still work). Set a valid MONGODB_URI in .env and refresh.' + 'MongoDB 未连接,搜索记录与种子缓存不会被保存(其余功能可用)。在 .env 配置正确的 MONGODB_URI 后刷新即可。': 'MongoDB not connected; search history and torrent cache will not be saved (other features still work). Set a valid MONGODB_URI in .env and refresh.', + + // ===== 访问密码门禁 ===== + '需要密码': 'Password required', + '请输入访问密码以进入': 'Enter the access password to continue', + '访问密码': 'Access password', + '进入': 'Enter', + '验证中': 'Verifying…', + '密码错误': 'Wrong password', + '验证失败': 'Verification failed', + '请输入密码': 'Please enter the password' } export const en2zh: Record = Object.fromEntries( diff --git a/server/api/unlock.post.ts b/server/api/unlock.post.ts new file mode 100644 index 0000000..7de9399 --- /dev/null +++ b/server/api/unlock.post.ts @@ -0,0 +1,17 @@ +// 校验站点访问密码(来自环境变量 WEBPASSWORD),成功后写入 HTTP-only cookie +export default defineEventHandler(async (event) => { + const password = process.env.WEBPASSWORD + if (!password) return { enabled: false } + + const body = await readBody<{ password?: string }>(event) + if (body.password === password) { + setCookie(event, 'web_unlock', '1', { + httpOnly: true, + sameSite: 'lax', + path: '/', + maxAge: 60 * 60 * 24 * 30 + }) + return { ok: true } + } + throw createError({ statusCode: 401, statusMessage: 'invalid password' }) +}) diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts new file mode 100644 index 0000000..eacc8d9 --- /dev/null +++ b/server/middleware/auth.ts @@ -0,0 +1,30 @@ +// 站点级访问门禁:仅当配置了 WEBPASSWORD 时启用。 +// 验证通过后由 /api/unlock 写入 HTTP-only cookie,后续请求凭此放行。 +export default defineEventHandler((event) => { + const password = process.env.WEBPASSWORD + // 未配置密码则站点完全开放 + if (!password) return + + const url = (event.node.req.url || '').split('?')[0] + + // 解锁接口、门禁页与 Nuxt 运行所需资源始终放行 + if ( + url === '/api/unlock' || + url === '/gate' || + url.startsWith('/_nuxt') || + url.startsWith('/__nuxt') || + url.startsWith('/@vite') || + url.startsWith('/@fs') || + url === '/favicon.ico' || + url.startsWith('/favicon') + ) { + return + } + + if (getCookie(event, 'web_unlock') === '1') return + + if (url.startsWith('/api/')) { + throw createError({ statusCode: 401, statusMessage: 'unauthorized' }) + } + return sendRedirect(event, '/gate') +}) diff --git a/server/utils/mongo.ts b/server/utils/mongo.ts index f90c27a..d55f57b 100644 --- a/server/utils/mongo.ts +++ b/server/utils/mongo.ts @@ -8,9 +8,6 @@ let indexState: 'pending' | 'done' = 'pending' export const mongoUri = () => process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017' export const mongoDbName = () => process.env.MONGODB_DB || 'torrent_console' -/** 最近一次连接失败信息,用于在 UI 上提示用户去装 / 启 MongoDB */ -let lastError: { message: string; at: Date } | null = null - export async function getClient(): Promise { if (client) return client if (!connecting) { @@ -25,12 +22,10 @@ export async function getClient(): Promise { .then((cli) => { client = cli connecting = null - lastError = null return cli }) .catch((err: unknown) => { connecting = null - lastError = { message: err instanceof Error ? err.message : String(err), at: new Date() } next.close().catch(() => {}) throw err }) @@ -68,24 +63,14 @@ export async function downloadsCol(): Promise> { return (await getDb()).collection('downloads') } -/** 把驱动错误里的连接串 / 主机名脱敏,避免泄露凭证与数据库地址 */ -function sanitizeMongoError(raw: string): string { - return raw - .replace(/mongodb(\+srv)?:\/\/[^\s'"]+/gi, '***') - .replace(/[a-z0-9.-]+\.mongodb\.net(?::\d+)?/gi, '***') -} - -/** 供 /api/health 使用:探测 MongoDB 是否可用(不返回任何连接串 / 库名) */ +/** 供 /api/health 使用:探测 MongoDB 是否可用(只返回 ok,不暴露任何连接信息) */ export async function mongoStatus() { try { const db = await getDb() await db.command({ ping: 1 }) - return { ok: true, error: null as string | null } - } catch (err) { - return { - ok: false, - error: sanitizeMongoError(lastError?.message || (err instanceof Error ? err.message : String(err))) - } + return { ok: true } + } catch { + return { ok: false } } }