Files

364 lines
28 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { createHmac } from "node:crypto";
import { createHttpServer } from "../src/app/http.ts";
import { loadConfig } from "../src/config.ts";
import { createTotpSecret, encryptSecret, issueAccessToken, verifyAccessToken } from "../src/shared/auth.ts";
import { createStore } from "../src/store.ts";
function config(role = "operator") {
return { host: "127.0.0.1", port: 0, accessTokenSecret: "test-secret-for-security-tests", accessTokenTtlSeconds: 900, refreshCookieName: "refresh", adminEmail: "admin@example.com", adminPassword: "change-me-now", adminMfaRequired: false, userMfaRequired: false, adminRole: role };
}
test("ordinary-user verification enhancements default to disabled", () => {
const config = loadConfig({ NODE_ENV: "development", MIRAGENFLOW_ACCESS_TOKEN_SECRET: "default-test-secret" });
assert.equal(config.adminMfaRequired, false);
assert.equal(config.userMfaRequired, false);
assert.equal(config.userVerificationRequired, false);
assert.equal(config.captchaRequired, false);
assert.equal(config.adminAccounts?.[0]?.mfaRequired, false);
});
test("cookie security can be explicitly disabled for an HTTP reverse proxy", () => {
const productionEnv = {
NODE_ENV: "production",
MIRAGENFLOW_ACCESS_TOKEN_SECRET: "a-production-secret-that-is-long-enough",
MIRAGENFLOW_ADMIN_PASSWORD: "a-production-admin-password",
MIRAGENFLOW_ADMIN_MFA_SECRET: "JBSWY3DPEHPK3PXP",
MIRAGENFLOW_CHANNEL_ENCRYPTION_KEY: "a-production-channel-key",
MIRAGENFLOW_PERSISTENCE_ADAPTER: "postgres",
DATABASE_URL: "postgres://example",
MIRAGENFLOW_QUEUE_ADAPTER: "redis",
REDIS_URL: "redis://example",
};
const productionDefaults = loadConfig(productionEnv);
assert.equal(productionDefaults.cookieSecure, true);
assert.equal(loadConfig({ ...productionEnv, MIRAGENFLOW_COOKIE_SECURE: "false" }).cookieSecure, false);
assert.equal(loadConfig({ NODE_ENV: "development", MIRAGENFLOW_ACCESS_TOKEN_SECRET: "default-test-secret", MIRAGENFLOW_COOKIE_SECURE: "true" }).cookieSecure, true);
});
function totpCode(secret: string, now = Date.now()) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; let buffer = 0; let bits = 0; const bytes: number[] = [];
for (const character of secret) { const value = alphabet.indexOf(character); if (value < 0) continue; buffer = (buffer << 5) | value; bits += 5; if (bits >= 8) { bytes.push((buffer >>> (bits - 8)) & 255); bits -= 8; } }
const key = Buffer.from(bytes); const message = Buffer.alloc(8); message.writeBigInt64BE(BigInt(Math.floor(now / 30_000))); const digest = createHmac("sha1", key).update(message).digest(); const offset = digest[digest.length - 1] & 15; const value = ((digest[offset] & 127) << 24) | (digest[offset + 1] << 16) | (digest[offset + 2] << 8) | digest[offset + 3]; return String(value % 1_000_000).padStart(6, "0");
}
test("CAPTCHA challenge is hashed, bound, retry-limited, and one-time", async () => {
const store = createStore();
const server = createHttpServer(store, { ...config(), captchaRequired: true });
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address(); assert.ok(address && typeof address !== "string");
const base = `http://127.0.0.1:${address.port}`; const email = "captcha@example.com"; const deviceId = "captcha-device";
try {
const challengeResponse = await fetch(`${base}/api/v1/auth/challenge`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "register", target: email, deviceId }) });
assert.equal(challengeResponse.status, 200);
const challenge = (await challengeResponse.json() as { data: { challengeId: string; question: string } }).data;
const numbers = challenge.question.match(/(\d+) \+ (\d+)/); assert.ok(numbers);
const answer = String(Number(numbers[1]) + Number(numbers[2]));
const baseBody = { email, password: "password123", deviceId, captchaChallengeId: challenge.challengeId };
const wrong = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ ...baseBody, captchaAnswer: "0" }) });
assert.equal(wrong.status, 422);
const registered = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ ...baseBody, captchaAnswer: answer }) });
assert.equal(registered.status, 201);
const reused = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ ...baseBody, email: "captcha-second@example.com", captchaAnswer: answer }) });
assert.equal(reused.status, 422);
assert.equal(store.captchaChallenges.get(challenge.challengeId)?.consumedAt !== undefined, true);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("administrator MFA retry is exempt only after a valid CAPTCHA-bound challenge", async () => {
const secret = "JBSWY3DPEHPK3PXP";
const store = createStore();
const server = createHttpServer(store, { ...config(), captchaRequired: true, adminMfaRequired: true, adminMfaSecret: secret });
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address(); assert.ok(address && typeof address !== "string");
const base = `http://127.0.0.1:${address.port}`;
try {
const challengeResponse = await fetch(`${base}/api/v1/auth/challenge`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "admin-login", target: "admin@example.com", deviceId: "admin-browser" }) });
const challenge = (await challengeResponse.json() as { data: { challengeId: string; question: string } }).data; const numbers = challenge.question.match(/(\d+) \+ (\d+)/); assert.ok(numbers); const answer = String(Number(numbers[1]) + Number(numbers[2]));
const first = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now", deviceId: "admin-browser", captchaChallengeId: challenge.challengeId, captchaAnswer: answer }) });
const firstData = (await first.json() as { data: { mfaRequired?: boolean; challengeId?: string } }).data; assert.equal(first.status, 200); assert.equal(firstData.mfaRequired, true); assert.ok(firstData.challengeId);
const second = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now", deviceId: "admin-browser", challengeId: firstData.challengeId, mfaCode: totpCode(secret) }) });
const secondData = (await second.json() as { data: { accessToken?: string } }).data; assert.equal(second.status, 200); assert.ok(secondData.accessToken);
} finally { await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); }
});
test("refresh restores a missing legacy CSRF cookie without weakening business writes", async () => {
const { store, server, base } = await start("super_admin");
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
assert.equal(login.status, 200);
const setCookies = login.headers.getSetCookie();
const refresh = setCookies.find((value) => value.startsWith("refresh_admin="));
assert.ok(refresh);
const legacyCookie = refresh.split(";", 1)[0];
const refreshed = await fetch(`${base}/api/v1/admin/auth/refresh`, { method: "POST", headers: { cookie: `${legacyCookie}` } });
assert.equal(refreshed.status, 200);
const restoredCookies = refreshed.headers.getSetCookie();
const nextRefresh = restoredCookies.find((value) => value.startsWith("refresh_admin="));
assert.ok(nextRefresh);
assert.equal(restoredCookies.some((value) => value.startsWith("miragenflow_csrf_admin=") && !value.includes("HttpOnly")), true);
const refreshedPayload = await refreshed.json() as { data: { accessToken: string } };
const protectedWrite = await fetch(`${base}/api/v1/admin/settings`, { method: "PATCH", headers: { cookie: `${nextRefresh!.split(";", 1)[0]}`, authorization: `Bearer ${refreshedPayload.data.accessToken}`, "content-type": "application/json", "idempotency-key": "legacy-csrf-write" }, body: JSON.stringify({}) });
assert.equal(protectedWrite.status, 403);
} finally { await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); }
});
async function start(role = "operator") {
const store = createStore();
const server = createHttpServer(store, config(role));
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
assert.ok(address && typeof address !== "string");
return { store, server, base: `http://127.0.0.1:${address.port}` };
}
test("admin RBAC rejects finance access and redacts support channel internals", async () => {
const { store, server, base } = await start("support");
try {
store.channels[0].providerName = "internal-provider";
store.channels[0].providerModelId = "internal-model";
store.channels[0].baseUrl = "https://provider.example.com";
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
assert.ok(loginPayload.data.accessToken);
const auth = { authorization: `Bearer ${loginPayload.data.accessToken}` };
const finance = await fetch(`${base}/api/v1/admin/billing/ledger`, { headers: auth });
assert.equal(finance.status, 403);
const channels = await fetch(`${base}/api/v1/admin/channels`, { headers: auth });
assert.equal(channels.status, 200);
const payload = await channels.json() as { data: { items: Array<Record<string, unknown>> } };
assert.equal(payload.data.items[0].providerModelId, undefined);
assert.equal(payload.data.items[0].baseUrl, undefined);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("payment provider credentials are encrypted and redacted", async () => {
const { store, server, base } = await start("super_admin");
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } }; const auth = { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json", "idempotency-key": "payment-provider-create" };
const created = await fetch(`${base}/api/v1/admin/payment-providers`, { method: "POST", headers: auth, body: JSON.stringify({ id: "secure-adapter", name: "Secure adapter", mode: "live", secret: "payment-secret-value" }) });
assert.equal(created.status, 201); const createdPayload = await created.json() as { data: { item: Record<string, unknown> } }; assert.equal(createdPayload.data.item.secretRef, undefined); assert.equal(createdPayload.data.item.secretConfigured, true);
const stored = store.paymentProviders.get("secure-adapter"); assert.ok(typeof stored?.secretRef === "string"); assert.notEqual(stored?.secretRef, "payment-secret-value");
const listed = await fetch(`${base}/api/v1/admin/payment-providers`, { headers: { authorization: `Bearer ${loginPayload.data.accessToken}` } }); const listedPayload = await listed.json() as { data: { items: Array<Record<string, unknown>> } }; assert.equal(listedPayload.data.items[0].secretRef, undefined); assert.equal(listedPayload.data.items[0].secret, undefined);
} finally { await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); }
});
test("OpenAI image channel creation does not probe implicitly and explicit probing uses Bearer auth", async () => {
const { store, server, base } = await start("super_admin");
const previousFetch = globalThis.fetch;
const probeRequests: Array<{ method: string; authorization: string | null; url: string }> = [];
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
globalThis.fetch = async (input, init) => {
const url = String(input);
if (url.startsWith("https://1.1.1.1/")) {
probeRequests.push({ url, method: init?.method || "GET", authorization: new Headers(init?.headers).get("authorization") });
return new Response(JSON.stringify({ data: [{ id: "gpt-image-2" }, { id: "gpt-image-2-2k" }] }), { status: 200, headers: { "content-type": "application/json" } });
}
return previousFetch(input, init);
};
const auth = { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json" };
const created = await fetch(`${base}/api/v1/admin/channels`, { method: "POST", headers: { ...auth, "idempotency-key": "openai-channel-create" }, body: JSON.stringify({ label: "OpenAI 图片渠道", providerType: "openai-images", providerModelId: "gpt-image-2", baseUrl: "https://1.1.1.1/v1", secretRef: "k", resolutionModelMap: { "1K": "gpt-image-2", "2K": "gpt-image-2-2k", invalid: "should-drop" } }) });
assert.equal(created.status, 201);
const createdPayload = await created.json() as { data: { item: Record<string, unknown> } };
assert.equal(createdPayload.data.item.secretRef, "configured");
assert.equal(createdPayload.data.item.baseUrl, "https://1.1.1.1/v1");
assert.deepEqual(createdPayload.data.item.resolutionModelMap, { "1K": "gpt-image-2", "2K": "gpt-image-2-2k" });
const channel = store.channels.find((item) => item.id === createdPayload.data.item.id);
assert.ok(channel);
assert.notEqual(channel.secretRef, "k");
assert.equal(probeRequests.length, 0);
const probe = await fetch(`${base}/api/v1/admin/channels/probe`, { method: "POST", headers: { ...auth, "idempotency-key": "openai-channel-probe" }, body: JSON.stringify({ providerType: "openai-images", baseUrl: "https://1.1.1.1/v1", secretRef: "k" }) });
assert.equal(probe.status, 200);
assert.equal(probeRequests[0]?.url, "https://1.1.1.1/v1/models");
assert.equal(probeRequests[0]?.method, "GET");
assert.equal(probeRequests[0]?.authorization, "Bearer k");
const invalid = await fetch(`${base}/api/v1/admin/channels`, { method: "POST", headers: { ...auth, "idempotency-key": "openai-channel-http" }, body: JSON.stringify({ label: "不安全渠道", providerType: "openai-images", providerModelId: "gpt-image-2", baseUrl: "http://1.1.1.1/v1", secretRef: "1234567890123456" }) });
assert.equal(invalid.status, 201);
assert.equal(store.channels.some((item) => item.label === "不安全渠道"), true);
} finally {
globalThis.fetch = previousFetch;
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("admin channel probe reports whether the upstream request was attempted and why it failed", async () => {
const { server, base } = await start("super_admin");
const previousFetch = globalThis.fetch;
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
let upstreamRequest: { method?: string; authorization?: string | null } | undefined;
globalThis.fetch = async (input, init) => {
if (String(input) === "https://1.1.1.1/v1/models") {
upstreamRequest = { method: init?.method, authorization: new Headers(init?.headers).get("authorization") };
return new Response(JSON.stringify({ error: { message: "invalid api key" } }), { status: 401, headers: { "content-type": "application/json" } });
}
return previousFetch(input, init);
};
const response = await fetch(`${base}/api/v1/admin/channels/probe`, {
method: "POST",
headers: { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json", "idempotency-key": "channel-probe-failure-details" },
body: JSON.stringify({ providerType: "openai-images", baseUrl: "https://1.1.1.1/v1", secretRef: "short-key" }),
});
assert.equal(response.status, 200);
const payload = await response.json() as { data: { item: { healthy: boolean; requestAttempted: boolean; requestPath: string; error?: string } } };
assert.equal(payload.data.item.healthy, false);
assert.equal(payload.data.item.requestAttempted, true);
assert.equal(payload.data.item.requestPath, "/v1/models");
assert.match(payload.data.item.error || "", /API Key/);
assert.deepEqual(upstreamRequest, { method: "GET", authorization: "Bearer short-key" });
} finally {
globalThis.fetch = previousFetch;
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("admin channel probe classifies transport causes without exposing credentials", async () => {
const { server, base } = await start("super_admin");
const previousFetch = globalThis.fetch;
const secret = "transport-secret-value";
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
const cases = [
{ code: "ECONNREFUSED", text: "拒绝建立连接" },
{ code: "ECONNRESET", text: "连接被中断" },
{ code: "CERT_HAS_EXPIRED", text: "TLS" },
{ code: "UND_ERR_CONNECT_TIMEOUT", text: "超时" },
];
for (const [index, item] of cases.entries()) {
globalThis.fetch = async (input, init) => {
if (String(input) === "https://1.1.1.1/v1/models") throw Object.assign(new TypeError("fetch failed"), { cause: { code: item.code } });
return previousFetch(input, init);
};
const response = await fetch(`${base}/api/v1/admin/channels/probe`, { method: "POST", headers: { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json", "idempotency-key": `transport-cause-${index}` }, body: JSON.stringify({ providerType: "openai-images", baseUrl: "https://1.1.1.1/v1", secretRef: secret }) });
const payload = await response.json() as { data: { item: { healthy: boolean; requestAttempted: boolean; error?: string } } };
assert.equal(response.status, 200);
assert.equal(payload.data.item.healthy, false);
assert.equal(payload.data.item.requestAttempted, true);
assert.match(payload.data.item.error || "", new RegExp(item.text));
assert.doesNotMatch(payload.data.item.error || "", /transport-secret-value|Bearer/);
}
} finally {
globalThis.fetch = previousFetch;
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("unsupported generic channel probes do not infer health from a configured key", async () => {
const { server, base } = await start("super_admin");
const previousFetch = globalThis.fetch;
let upstreamCalls = 0;
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
globalThis.fetch = async (input, init) => {
if (String(input).startsWith("https://generic.example.com/")) upstreamCalls += 1;
return previousFetch(input, init);
};
const response = await fetch(`${base}/api/v1/admin/channels/probe`, {
method: "POST",
headers: { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json", "idempotency-key": "generic-channel-probe" },
body: JSON.stringify({ providerType: "generic", baseUrl: "https://generic.example.com/v1", secretRef: "configured-key" }),
});
assert.equal(response.status, 200);
const payload = await response.json() as { data: { item: { healthy: boolean; requestAttempted: boolean; error?: string } } };
assert.equal(payload.data.item.healthy, false);
assert.equal(payload.data.item.requestAttempted, false);
assert.match(payload.data.item.error || "", /不支持自动模型列表探活/);
assert.equal(upstreamCalls, 0);
} finally {
globalThis.fetch = previousFetch;
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("provider probe reports blocked addresses before an upstream request", async () => {
const { server, base } = await start("super_admin");
const previousFetch = globalThis.fetch;
const previousAllowLocal = process.env.MIRAGENFLOW_ALLOW_LOCAL_PROVIDER_URLS;
let upstreamCalls = 0;
try {
const login = await fetch(`${base}/api/v1/admin/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "admin@example.com", password: "change-me-now" }) });
const loginPayload = await login.json() as { data: { accessToken: string } };
process.env.MIRAGENFLOW_ALLOW_LOCAL_PROVIDER_URLS = "false";
globalThis.fetch = async (input, init) => {
if (String(input).startsWith("https://127.0.0.1")) upstreamCalls += 1;
return previousFetch(input, init);
};
const response = await fetch(`${base}/api/v1/admin/channels/probe`, {
method: "POST",
headers: { authorization: `Bearer ${loginPayload.data.accessToken}`, "content-type": "application/json", "idempotency-key": "dns-channel-probe" },
body: JSON.stringify({ providerType: "openai-images", baseUrl: "https://127.0.0.1/v1", secretRef: "configured-key" }),
});
assert.equal(response.status, 200);
const payload = await response.json() as { data: { item: { healthy: boolean; requestAttempted: boolean; error?: string } } };
assert.equal(payload.data.item.healthy, false);
assert.equal(payload.data.item.requestAttempted, false);
assert.match(payload.data.item.error || "", /不允许指向本机/);
assert.match(payload.data.item.error || "", /未发起|尚未发起/);
assert.equal(upstreamCalls, 0);
} finally {
globalThis.fetch = previousFetch;
if (previousAllowLocal === undefined) delete process.env.MIRAGENFLOW_ALLOW_LOCAL_PROVIDER_URLS;
else process.env.MIRAGENFLOW_ALLOW_LOCAL_PROVIDER_URLS = previousAllowLocal;
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("WebDAV If-Match mismatch returns a conflict before any remote request", async () => {
const { store, server, base } = await start();
try {
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "webdav-conflict@example.com", password: "password123" }) });
const registered = await register.json() as { data: { userId: string; devVerificationCode: string } };
await fetch(`${base}/api/v1/auth/verify-email`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ userId: registered.data.userId, code: registered.data.devVerificationCode }) });
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "webdav-conflict@example.com", password: "password123" }) });
const session = await login.json() as { data: { accessToken: string } };
const auth = { authorization: `Bearer ${session.data.accessToken}`, "content-type": "application/json" };
store.webdav.set(registered.data.userId, { userId: registered.data.userId, configured: true, directory: "miragenflow", state: "ready", retentionDays: 30, encryptedUrl: encryptSecret("https://dav.example.com", config().accessTokenSecret) });
store.webdavFiles.set(`${registered.data.userId}:canvas/manifest.json`, { userId: registered.data.userId, path: "canvas/manifest.json", mimeType: "application/json", data: "eA==", checksum: "a".repeat(64), etag: '"remote-v2"', version: 2, updatedAt: new Date().toISOString() });
const response = await fetch(`${base}/api/v1/me/webdav/file`, { method: "PUT", headers: auth, body: JSON.stringify({ path: "canvas/manifest.json", mimeType: "application/json", data: "eA==", ifMatch: '"stale"' }) });
assert.equal(response.status, 409);
const payload = await response.json() as { error?: { code?: string } };
assert.equal(payload.error?.code, "VERSION_CONFLICT");
assert.equal([...store.webdavJobs.values()].length, 0);
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
});
test("WebDAV rejects traversal paths before creating a retry job", async () => {
const { store, server, base } = await start();
try {
const register = await fetch(`${base}/api/v1/auth/register`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "webdav-path@example.com", password: "password123" }) });
const registered = await register.json() as { data: { userId: string; devVerificationCode: string } };
await fetch(`${base}/api/v1/auth/verify-email`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ userId: registered.data.userId, code: registered.data.devVerificationCode }) });
const login = await fetch(`${base}/api/v1/auth/login`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email: "webdav-path@example.com", password: "password123" }) });
const session = await login.json() as { data: { accessToken: string } };
store.webdav.set(registered.data.userId, { userId: registered.data.userId, configured: true, directory: "miragenflow", state: "ready", retentionDays: 30, encryptedUrl: encryptSecret("https://dav.example.com", config().accessTokenSecret) });
const response = await fetch(`${base}/api/v1/me/webdav/file`, { method: "PUT", headers: { authorization: `Bearer ${session.data.accessToken}`, "content-type": "application/json" }, body: JSON.stringify({ path: "../escape", data: "eA==" }) });
assert.equal(response.status, 422);
assert.equal(store.webdavJobs.size, 0);
} finally { await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); }
});
test("access tokens reject a signed token with an unexpected JOSE header", () => {
const token = issueAccessToken("user-1", "user", "header-test-secret", 900);
const [, body] = token.split(".");
const alteredHeader = Buffer.from(JSON.stringify({ alg: "HS512", typ: "JWT" })).toString("base64url");
const alteredBody = `${alteredHeader}.${body}`;
const alteredSignature = createHmac("sha256", "header-test-secret").update(alteredBody).digest("base64url");
assert.throws(() => verifyAccessToken(`${alteredBody}.${alteredSignature}`, "header-test-secret", "user"), /登录状态已失效/);
assert.doesNotThrow(() => verifyAccessToken(token, "header-test-secret", "user"));
});