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 }
}
}