fix: protect self update and migration retry

This commit is contained in:
digua authored and digua committed 2026-05-26 23:19:47 +08:00
1 parent e05016c512
commit 19a45f9e5f
5 files changed
+51 -2

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { createServer } from './server'
import { setAuthToken } from './auth'
describe('CLI HTTP auth hook', () => {
it('requires bearer token for the CLI self-update endpoint under /_web', async () => {
setAuthToken('clb_test_token')
const app = createServer()
app.post('/_web/system/update', async () => ({ success: true }))
const response = await app.inject({ method: 'POST', url: '/_web/system/update' })
await app.close()
assert.equal(response.statusCode, 401)
})
})
+11
View File
@@ -10,6 +10,7 @@ import { unauthorized, errorResponse } from './errors'
let cachedToken: string | null = null
let webModeEnabled = false
const protectedWebPaths = new Set(['/_web/system/update'])
/**
* 设置 auth hook 使用的 token(由 server 启动时注入)
@@ -36,12 +37,22 @@ function safeTokenCompare(a: string, b: string): boolean {
export async function authHook(request: FastifyRequest, reply: FastifyReply): Promise<void> {
if (!cachedToken) return
if (protectedWebPaths.has(request.url.split('?')[0])) {
return requireBearerToken(request, reply)
}
// /_web/ internal API: no auth required (same-origin Web UI only)
if (request.url.startsWith('/_web/')) return
// Web mode: only /api/ routes require auth; static files and SPA are public
if (webModeEnabled && !request.url.startsWith('/api/')) return
return requireBearerToken(request, reply)
}
function requireBearerToken(request: FastifyRequest, reply: FastifyReply): void {
if (!cachedToken) return
const authHeader = request.headers.authorization
if (!authHeader || !authHeader.startsWith('Bearer ')) {
const err = unauthorized()
+10
View File
@@ -0,0 +1,10 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { shouldMarkUnifiedDirMigrationDone } from './paths'
describe('desktop path migration', () => {
it('marks unified directory migration done only when no directory failed', () => {
assert.equal(shouldMarkUnifiedDirMigrationDone([]), true)
assert.equal(shouldMarkUnifiedDirMigrationDone(['settings']), false)
})
})
+7 -1
View File
@@ -573,6 +573,10 @@ export function removeLegacyDir(): boolean {
const SYSTEM_SUBDIRS = ['ai', 'settings', 'cache', 'logs', 'temp', 'nlp']
export function shouldMarkUnifiedDirMigrationDone(failedDirs: string[]): boolean {
return failedDirs.length === 0
}
/**
* 检测是否需要从 Electron 旧目录结构迁移到新的双根目录结构
*
@@ -712,7 +716,9 @@ export function migrateToUnifiedDirs(): { success: boolean; error?: string } {
writeMigrationLog(getLogsDir(), summary, ensureDir)
console.log(`[Migration] ${summary}`)
writeConfigField('data', 'electron_migration_done', true)
if (shouldMarkUnifiedDirMigrationDone(failedDirs)) {
writeConfigField('data', 'electron_migration_done', true)
}
return { success: failedDirs.length === 0 }
} catch (error) {
+6 -1
View File
@@ -74,7 +74,12 @@ export class WebPlatformAdapter implements PlatformAdapter {
async performUpdate(): Promise<PerformUpdateResult> {
try {
const resp = await fetch('/_web/system/update', { method: 'POST' })
const configResp = await fetch('/_web/automation/config')
const config = (await configResp.json()) as { token?: string }
const headers: Record<string, string> = {}
if (config.token) headers.Authorization = `Bearer ${config.token}`
const resp = await fetch('/_web/system/update', { method: 'POST', headers })
return await resp.json()
} catch (err) {
return { success: false, error: err instanceof Error ? err.message : String(err) }