mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 17:34:07 +00:00
fix(zalo): bound hosted media expiry clocks
This commit is contained in:
@@ -151,6 +151,50 @@ describe("zalo outbound hosted media", () => {
|
||||
expect(secondResponse.res.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it("rejects hosted media preparation when the expiry would exceed a valid Date", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date(8_640_000_000_000_000));
|
||||
try {
|
||||
await expect(
|
||||
prepareHostedZaloMediaUrl({
|
||||
mediaUrl: "https://example.com/photo.png",
|
||||
webhookUrl: "https://gateway.example.com/zalo-webhook",
|
||||
maxBytes: 1024,
|
||||
}),
|
||||
).rejects.toThrow(/expiry/);
|
||||
|
||||
expect(loadOutboundMediaFromUrlMock).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("does not serve hosted media when the current clock is invalid", async () => {
|
||||
const hostedUrl = await prepareHostedZaloMediaUrl({
|
||||
mediaUrl: "https://example.com/photo.png",
|
||||
webhookUrl: "https://gateway.example.com/zalo-webhook",
|
||||
maxBytes: 1024,
|
||||
});
|
||||
const { pathname, search } = new URL(hostedUrl);
|
||||
const response = createMockResponse();
|
||||
const dateNow = vi.spyOn(Date, "now").mockReturnValue(Number.NaN);
|
||||
try {
|
||||
const handled = await tryHandleHostedZaloMediaRequest(
|
||||
{
|
||||
method: "GET",
|
||||
url: `${pathname}${search}`,
|
||||
} as never,
|
||||
response.res as never,
|
||||
);
|
||||
|
||||
expect(handled).toBe(true);
|
||||
expect(response.res.statusCode).toBe(410);
|
||||
expect(response.res.end).toHaveBeenCalledWith("Expired");
|
||||
} finally {
|
||||
dateNow.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects hosted media requests with the wrong token", async () => {
|
||||
const hostedUrl = await prepareHostedZaloMediaUrl({
|
||||
mediaUrl: "https://example.com/photo.png",
|
||||
|
||||
@@ -3,6 +3,10 @@ import { rmSync } from "node:fs";
|
||||
import { readdir, readFile, stat, unlink } from "node:fs/promises";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
asDateTimestampMs,
|
||||
resolveExpiresAtMsFromDurationMs,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
|
||||
import { privateFileStore } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
@@ -64,6 +68,10 @@ async function deleteHostedZaloMediaEntry(id: string): Promise<void> {
|
||||
}
|
||||
|
||||
async function cleanupExpiredHostedZaloMedia(nowMs = Date.now()): Promise<void> {
|
||||
const now = asDateTimestampMs(nowMs);
|
||||
if (now === undefined) {
|
||||
return;
|
||||
}
|
||||
let fileNames: string[];
|
||||
try {
|
||||
fileNames = await readdir(ZALO_OUTBOUND_MEDIA_DIR);
|
||||
@@ -79,7 +87,8 @@ async function cleanupExpiredHostedZaloMedia(nowMs = Date.now()): Promise<void>
|
||||
try {
|
||||
const metadataRaw = await readFile(resolveHostedZaloMediaMetadataPath(id), "utf8");
|
||||
const metadata = JSON.parse(metadataRaw) as HostedZaloMediaMetadata;
|
||||
if (metadata.expiresAt <= nowMs) {
|
||||
const expiresAt = asDateTimestampMs(metadata.expiresAt);
|
||||
if (expiresAt === undefined || expiresAt <= now) {
|
||||
await deleteHostedZaloMediaEntry(id);
|
||||
}
|
||||
} catch {
|
||||
@@ -141,6 +150,15 @@ export async function prepareHostedZaloMediaUrl(params: {
|
||||
await ensureHostedZaloMediaDir();
|
||||
await cleanupExpiredHostedZaloMedia();
|
||||
|
||||
const now = asDateTimestampMs(Date.now());
|
||||
const expiresAt =
|
||||
now === undefined
|
||||
? undefined
|
||||
: resolveExpiresAtMsFromDurationMs(ZALO_OUTBOUND_MEDIA_TTL_MS, { nowMs: now });
|
||||
if (expiresAt === undefined) {
|
||||
throw new Error("Zalo outbound media expiry could not be resolved");
|
||||
}
|
||||
|
||||
const media = await loadOutboundMediaFromUrl(params.mediaUrl, {
|
||||
maxBytes: params.maxBytes,
|
||||
...(params.proxyUrl ? { proxyUrl: params.proxyUrl } : {}),
|
||||
@@ -161,7 +179,7 @@ export async function prepareHostedZaloMediaUrl(params: {
|
||||
routePath,
|
||||
token,
|
||||
contentType: media.contentType,
|
||||
expiresAt: Date.now() + ZALO_OUTBOUND_MEDIA_TTL_MS,
|
||||
expiresAt,
|
||||
} satisfies HostedZaloMediaMetadata);
|
||||
} catch (error) {
|
||||
await deleteHostedZaloMediaEntry(id);
|
||||
@@ -210,7 +228,9 @@ export async function tryHandleHostedZaloMediaRequest(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entry.metadata.expiresAt <= Date.now()) {
|
||||
const now = asDateTimestampMs(Date.now());
|
||||
const expiresAt = asDateTimestampMs(entry.metadata.expiresAt);
|
||||
if (now === undefined || expiresAt === undefined || expiresAt <= now) {
|
||||
await deleteHostedZaloMediaEntry(id);
|
||||
res.statusCode = 410;
|
||||
res.end("Expired");
|
||||
|
||||
Reference in New Issue
Block a user