fix: restore security hardening compatibility and validation

Restore authorized rule reveal, explicit full HTTP capture and retention, video task business fields, and valid payment URLs. Add opt-in credential preservation for trusted recovery, fix frontend type contracts and async races, and eliminate PostgreSQL test fixture resource leaks. Document audit coverage and successful fmt and CI-scoped Clippy checks.
This commit is contained in:
elky
2026-09-07 21:14:27 +08:00
parent a5c3699ae9
commit a90d564931
191 changed files with 6785 additions and 1643 deletions
+17 -21
View File
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { AxiosAdapter, AxiosInstance, InternalAxiosRequestConfig } from 'axios'
import type { AxiosAdapter, InternalAxiosRequestConfig } from 'axios'
import apiClient, {
AUTH_SESSION_SIGNAL_KEY,
@@ -8,10 +8,6 @@ import apiClient, {
} from '@/api/client'
import { cache, cachedRequest } from '@/utils/cache'
type TestableApiClient = typeof apiClient & {
client: AxiosInstance
}
describe('apiClient auth state change event', () => {
beforeEach(() => {
localStorage.clear()
@@ -55,10 +51,10 @@ describe('apiClient auth state change event', () => {
})
it('restores a session through the refresh cookie and stores the result in memory only', async () => {
const rawClient = apiClient as TestableApiClient
const previousAdapter = rawClient.client.defaults.adapter
const rawClient = apiClient['client']
const previousAdapter = rawClient.defaults.adapter
rawClient.client.defaults.adapter = (async (config: InternalAxiosRequestConfig) => ({
rawClient.defaults.adapter = (async (config: InternalAxiosRequestConfig) => ({
data: { access_token: 'restored-access-token' },
status: 200,
statusText: 'OK',
@@ -72,16 +68,16 @@ describe('apiClient auth state change event', () => {
expect(localStorage.getItem('access_token')).toBeNull()
expect(sessionStorage.getItem('access_token')).toBeNull()
} finally {
rawClient.client.defaults.adapter = previousAdapter
rawClient.defaults.adapter = previousAdapter
}
})
it('does not resurrect a session when logout wins an in-flight restore', async () => {
const rawClient = apiClient as TestableApiClient
const previousAdapter = rawClient.client.defaults.adapter
const rawClient = apiClient['client']
const previousAdapter = rawClient.defaults.adapter
let resolveRefresh!: (response: Awaited<ReturnType<AxiosAdapter>>) => void
rawClient.client.defaults.adapter = (() => new Promise((resolve) => {
rawClient.defaults.adapter = (() => new Promise((resolve) => {
resolveRefresh = resolve
})) as AxiosAdapter
@@ -100,7 +96,7 @@ describe('apiClient auth state change event', () => {
await expect(restore).rejects.toThrow('Auth state changed')
expect(apiClient.getToken()).toBeNull()
} finally {
rawClient.client.defaults.adapter = previousAdapter
rawClient.defaults.adapter = previousAdapter
}
})
@@ -141,11 +137,11 @@ describe('apiClient auth state change event', () => {
})
it('sends auth refresh without a request body', async () => {
const rawClient = apiClient as TestableApiClient
const previousAdapter = rawClient.client.defaults.adapter
const rawClient = apiClient['client']
const previousAdapter = rawClient.defaults.adapter
const requests: InternalAxiosRequestConfig[] = []
rawClient.client.defaults.adapter = (async (config: InternalAxiosRequestConfig) => {
rawClient.defaults.adapter = (async (config: InternalAxiosRequestConfig) => {
requests.push(config)
return {
data: { access_token: 'new-access-token' },
@@ -165,16 +161,16 @@ describe('apiClient auth state change event', () => {
expect(requests[0].method).toBe('post')
expect(requests[0].data).toBeUndefined()
} finally {
rawClient.client.defaults.adapter = previousAdapter
rawClient.defaults.adapter = previousAdapter
}
})
it('authenticates protected gateway operational requests', async () => {
const rawClient = apiClient as TestableApiClient
const previousAdapter = rawClient.client.defaults.adapter
const rawClient = apiClient['client']
const previousAdapter = rawClient.defaults.adapter
const requests: InternalAxiosRequestConfig[] = []
rawClient.client.defaults.adapter = (async (config: InternalAxiosRequestConfig) => {
rawClient.defaults.adapter = (async (config: InternalAxiosRequestConfig) => {
requests.push(config)
return {
data: '',
@@ -193,7 +189,7 @@ describe('apiClient auth state change event', () => {
expect(requests[0].headers.Authorization).toBe('Bearer operational-access-token')
expect(requests[0].headers['X-Client-Device-Id']).toBeTruthy()
} finally {
rawClient.client.defaults.adapter = previousAdapter
rawClient.defaults.adapter = previousAdapter
}
})
})
@@ -0,0 +1,26 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { revealEndpointRules } from '../endpoints/endpoints'
const client = vi.hoisted(() => ({ get: vi.fn() }))
vi.mock('../client', () => ({ default: client }))
beforeEach(() => {
client.get.mockReset().mockResolvedValue({ data: { header_rules: [], body_rules: [], response_header_rules: [] } })
})
describe('endpoint rule reveal API', () => {
it('uses the scoped route and forwards cancellation', async () => {
const controller = new AbortController()
await revealEndpointRules('endpoint/with?reserved', controller.signal)
expect(client.get).toHaveBeenCalledWith(
'/api/admin/endpoints/endpoint%2Fwith%3Freserved/rules/reveal',
{ signal: controller.signal },
)
})
it('fetches each reveal without retaining a cached response', async () => {
await revealEndpointRules('endpoint-1')
await revealEndpointRules('endpoint-1')
expect(client.get).toHaveBeenCalledTimes(2)
})
})