Files
Jeff Gu 8336649477 refactor(auth): 系统安全性增强
- 移除管理员凭证存储逻辑,改用 Cookie 管理会话
- 清理 admin-auth 中的存储函数,使用 Cookie 进行会话管理
- 更新 apiClient 请求逻辑,支持管理员 Cookie
- 修改 auth store,调整登录和验证逻辑
- 添加 Cookie 处理函数,确保安全性和兼容性
- 更新测试脚本,验证 Cookie 功能
2026-04-14 16:32:07 +08:00

229 lines
8.9 KiB
TypeScript

/**
* Mock upstream server that simulates OpenAI / Azure OpenAI / OpenAI Responses APIs.
* Usage: npx tsx tests/mock-upstream.ts
* Listens on port 9999.
*/
const PORT = 9999
const INSPECTABLE_REQUEST_KEYS = ["chat-completions", "messages", "models", "responses"] as const
type InspectableRequestKey = typeof INSPECTABLE_REQUEST_KEYS[number]
type CapturedRequest = {
body: unknown
headers: Record<string, string>
method: string
path: string
}
const capturedRequests = new Map<InspectableRequestKey, CapturedRequest>()
const toHeaderRecord = (headers: Headers): Record<string, string> => {
return Object.fromEntries(Array.from(headers.entries()).map(([key, value]) => [key.toLowerCase(), value]))
}
const getCaptureKey = (path: string): InspectableRequestKey | null => {
if (path.endsWith("/chat/completions")) {
return "chat-completions"
}
if (path.endsWith("/messages")) {
return "messages"
}
if (path.endsWith("/responses")) {
return "responses"
}
if (path.endsWith("/models")) {
return "models"
}
return null
}
const rememberRequest = (req: Request, path: string, body: unknown) => {
const captureKey = getCaptureKey(path)
if (!captureKey) {
return
}
capturedRequests.set(captureKey, {
body,
headers: toHeaderRecord(req.headers),
method: req.method,
path,
})
}
// --- OpenAI Chat Completions (non-stream) ---
const openaiChatResponse = (model: string) => ({
id: "chatcmpl-mock-001",
object: "chat.completion",
created: 1700000000,
model,
choices: [{
index: 0,
message: { role: "assistant", content: "Mock response from " + model },
finish_reason: "stop",
}],
usage: { prompt_tokens: 10, completion_tokens: 8, total_tokens: 18 },
})
// --- OpenAI Chat Completions (stream) ---
const openaiChatStreamChunks = (model: string) => [
`data: ${JSON.stringify({ id: "chatcmpl-mock-001", object: "chat.completion.chunk", model, choices: [{ index: 0, delta: { role: "assistant", content: "" }, finish_reason: null }], usage: null })}\n\n`,
`data: ${JSON.stringify({ id: "chatcmpl-mock-001", object: "chat.completion.chunk", model, choices: [{ index: 0, delta: { content: "Mock " }, finish_reason: null }], usage: null })}\n\n`,
`data: ${JSON.stringify({ id: "chatcmpl-mock-001", object: "chat.completion.chunk", model, choices: [{ index: 0, delta: { content: "stream " }, finish_reason: null }], usage: null })}\n\n`,
`data: ${JSON.stringify({ id: "chatcmpl-mock-001", object: "chat.completion.chunk", model, choices: [{ index: 0, delta: { content: "response" }, finish_reason: null }], usage: null })}\n\n`,
`data: ${JSON.stringify({ id: "chatcmpl-mock-001", object: "chat.completion.chunk", model, choices: [{ index: 0, delta: {}, finish_reason: "stop" }], usage: { prompt_tokens: 10, completion_tokens: 3, total_tokens: 13 } })}\n\n`,
`data: [DONE]\n\n`,
]
// --- OpenAI Responses (non-stream) ---
const openaiResponsesResponse = (model: string) => ({
id: "resp-mock-001",
object: "response",
created_at: 1700000000,
status: "completed",
model,
output: [{
id: "msg-mock-001",
type: "message",
status: "completed",
content: [{ type: "output_text", text: "Mock responses output" }],
role: "assistant",
}],
usage: { input_tokens: 8, output_tokens: 5, total_tokens: 13 },
})
// --- OpenAI Responses (stream) ---
const openaiResponsesStreamChunks = (model: string) => {
const respId = "resp-mock-001"
const msgId = "msg-mock-001"
return [
`event: response.created\ndata: ${JSON.stringify({ type: "response.created", response: { id: respId, object: "response", model, status: "in_progress", output: [], usage: null } })}\n\n`,
`event: response.output_item.added\ndata: ${JSON.stringify({ type: "response.output_item.added", item: { id: msgId, type: "message", status: "in_progress", content: [], role: "assistant" }, output_index: 0 })}\n\n`,
`event: response.output_text.delta\ndata: ${JSON.stringify({ type: "response.output_text.delta", delta: "Mock ", item_id: msgId, output_index: 0, content_index: 0 })}\n\n`,
`event: response.output_text.delta\ndata: ${JSON.stringify({ type: "response.output_text.delta", delta: "stream", item_id: msgId, output_index: 0, content_index: 0 })}\n\n`,
`event: response.completed\ndata: ${JSON.stringify({ type: "response.completed", response: { id: respId, object: "response", model, status: "completed", output: [{ id: msgId, type: "message", status: "completed", content: [{ type: "output_text", text: "Mock stream" }], role: "assistant" }], usage: { input_tokens: 8, output_tokens: 4, total_tokens: 12 } } })}\n\n`,
]
}
// --- Claude Messages (non-stream) ---
const claudeMessagesResponse = (model: string) => ({
id: "msg-mock-claude-001",
type: "message",
role: "assistant",
model,
content: [{ type: "text", text: "Mock Claude response" }],
stop_reason: "end_turn",
stop_sequence: null,
usage: { input_tokens: 12, output_tokens: 6 },
})
// --- Claude Messages (stream) ---
const claudeMessagesStreamChunks = (model: string) => [
`event: message_start\ndata: ${JSON.stringify({ type: "message_start", message: { id: "msg-mock-claude-001", type: "message", role: "assistant", model, content: [], stop_reason: null, usage: { input_tokens: 12, output_tokens: 0 } } })}\n\n`,
`event: content_block_start\ndata: ${JSON.stringify({ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } })}\n\n`,
`event: content_block_delta\ndata: ${JSON.stringify({ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "Mock Claude stream" } })}\n\n`,
`event: content_block_stop\ndata: ${JSON.stringify({ type: "content_block_stop", index: 0 })}\n\n`,
`event: message_delta\ndata: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 4 } })}\n\n`,
`event: message_stop\ndata: ${JSON.stringify({ type: "message_stop" })}\n\n`,
]
const server = Bun.serve({
port: PORT,
async fetch(req) {
const url = new URL(req.url)
const path = url.pathname
const inspectKey = url.searchParams.get("key")
if (path === "/__reset" && req.method === "POST") {
capturedRequests.clear()
return Response.json({ ok: true })
}
if (path === "/__inspect" && req.method === "GET") {
if (!inspectKey || !INSPECTABLE_REQUEST_KEYS.includes(inspectKey as InspectableRequestKey)) {
return Response.json({
keys: INSPECTABLE_REQUEST_KEYS,
}, { status: 400 })
}
return Response.json(capturedRequests.get(inspectKey as InspectableRequestKey) || null)
}
const body = req.method === "POST" ? await req.json().catch(() => ({})) : {}
const model = (body as any).model || "mock-model"
const isStream = (body as any).stream === true
console.log(`[MOCK] ${req.method} ${path} model=${model} stream=${isStream}`)
rememberRequest(req, path, body)
// OpenAI Chat Completions
if (path.endsWith("/chat/completions")) {
if (isStream) {
const chunks = openaiChatStreamChunks(model)
const stream = new ReadableStream({
async start(controller) {
for (const chunk of chunks) {
controller.enqueue(new TextEncoder().encode(chunk))
await new Promise(r => setTimeout(r, 50))
}
controller.close()
}
})
return new Response(stream, {
headers: { "content-type": "text/event-stream", "cache-control": "no-cache" },
})
}
return Response.json(openaiChatResponse(model))
}
// OpenAI Responses API
if (path.endsWith("/responses")) {
if (isStream) {
const chunks = openaiResponsesStreamChunks(model)
const stream = new ReadableStream({
async start(controller) {
for (const chunk of chunks) {
controller.enqueue(new TextEncoder().encode(chunk))
await new Promise(r => setTimeout(r, 50))
}
controller.close()
}
})
return new Response(stream, {
headers: { "content-type": "text/event-stream", "cache-control": "no-cache" },
})
}
return Response.json(openaiResponsesResponse(model))
}
// Claude Messages API
if (path.endsWith("/messages")) {
if (isStream) {
const chunks = claudeMessagesStreamChunks(model)
const stream = new ReadableStream({
async start(controller) {
for (const chunk of chunks) {
controller.enqueue(new TextEncoder().encode(chunk))
await new Promise(r => setTimeout(r, 50))
}
controller.close()
}
})
return new Response(stream, {
headers: { "content-type": "text/event-stream", "cache-control": "no-cache" },
})
}
return Response.json(claudeMessagesResponse(model))
}
return new Response("Mock: unknown route " + path, { status: 404 })
},
})
console.log(`[MOCK] Upstream mock server running on http://localhost:${PORT}`)