Fix S3 presigned URL expiration exceeding AWS 7-day limit (#11191)

* Initial plan

* Fix S3 presigned URL expiration exceeding AWS 7-day limit

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

* Add comprehensive tests for S3 presigned URL expiration limits

Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tommoor <380914+tommoor@users.noreply.github.com>
This commit is contained in:
Copilot
2026-01-15 19:51:21 -05:00
committed by GitHub
parent 4d3f9bf0b4
commit 77ad224709
5 changed files with 64 additions and 2 deletions
+2 -1
View File
@@ -38,6 +38,7 @@ import Icon from "@shared/components/Icon";
import type { NavigationNode } from "@shared/types";
import { ExportContentType, TeamPreference } from "@shared/types";
import { getEventFiles } from "@shared/utils/files";
import { Week } from "@shared/utils/time";
import type UserMembership from "~/models/UserMembership";
import { client } from "~/utils/ApiClient";
import DocumentDelete from "~/scenes/DocumentDelete";
@@ -630,7 +631,7 @@ export const copyDocumentAsMarkdown = createAction({
if (document) {
const res = await client.post("/documents.export", {
id: document.id,
signedUrls: 3600 * 24 * 30, // 30 days
signedUrls: Week.seconds, // 7 days (AWS S3 max for presigned URLs)
});
copy(res.data);
toast.success(t("Markdown copied to clipboard"));
+7
View File
@@ -4,6 +4,7 @@ import type { PresignedPost } from "@aws-sdk/s3-presigned-post";
import omit from "lodash/omit";
import FileHelper from "@shared/editor/lib/FileHelper";
import { isBase64Url, isInternalUrl } from "@shared/utils/urls";
import { Week } from "@shared/utils/time";
import env from "@server/env";
import Logger from "@server/logging/Logger";
import type { RequestInit } from "@server/utils/fetch";
@@ -14,6 +15,12 @@ export default abstract class BaseStorage {
/** The default number of seconds until a signed URL expires. */
public static defaultSignedUrlExpires = 300;
/**
* The maximum number of seconds until a signed URL expires for S3 Signature V4.
* AWS S3 Signature V4 presigned URLs must have an expiration date less than one week in the future.
*/
public static maxSignedUrlExpires = Week.seconds;
/**
* Returns a presigned post for uploading files to the storage provider.
*
+40
View File
@@ -0,0 +1,40 @@
import { Week, Day } from "@shared/utils/time";
import BaseStorage from "./BaseStorage";
describe("S3Storage", () => {
describe("getSignedUrl expiration limits", () => {
it("should define maximum expiration as 7 days for AWS S3 Signature V4", () => {
// AWS S3 Signature V4 presigned URLs have a maximum expiration of 7 days
const maxExpiration = Week.seconds;
// Verify our constant matches AWS limit
expect(BaseStorage.maxSignedUrlExpires).toBe(maxExpiration);
expect(BaseStorage.maxSignedUrlExpires).toBe(604800); // 7 days in seconds
});
it("should have Week.seconds equal to 7 days", () => {
expect(Week.seconds).toBe(7 * 24 * 60 * 60);
expect(Week.seconds).toBe(604800);
});
it("should ensure 30 days exceeds the limit", () => {
const thirtyDays = 30 * Day.seconds;
expect(thirtyDays).toBeGreaterThan(BaseStorage.maxSignedUrlExpires);
expect(thirtyDays).toBe(2592000); // 30 days in seconds
});
it("should ensure 4 days is within the limit", () => {
const fourDays = 4 * Day.seconds;
expect(fourDays).toBeLessThan(BaseStorage.maxSignedUrlExpires);
expect(fourDays).toBe(345600); // 4 days in seconds
});
it("should clamp values that exceed the limit", () => {
const thirtyDays = 30 * Day.seconds;
const clampedValue = Math.min(thirtyDays, BaseStorage.maxSignedUrlExpires);
expect(clampedValue).toBe(BaseStorage.maxSignedUrlExpires);
expect(clampedValue).toBe(Week.seconds);
});
});
});
+4 -1
View File
@@ -152,8 +152,11 @@ export default class S3Storage extends BaseStorage {
if (isDocker) {
return `${this.getPublicEndpoint()}/${key}`;
} else {
// Ensure expiration does not exceed AWS S3 Signature V4 limit of 7 days
const clampedExpiresIn = Math.min(expiresIn, S3Storage.maxSignedUrlExpires);
const command = new GetObjectCommand(params);
const url = await getSignedUrl(this.client, command, { expiresIn });
const url = await getSignedUrl(this.client, command, { expiresIn: clampedExpiresIn });
if (env.AWS_S3_ACCELERATE_URL) {
return url.replace(
+11
View File
@@ -27,3 +27,14 @@ export class Day {
/** Minutes in a day */
public static minutes = 24 * Hour.minutes;
}
export class Week {
/** Milliseconds in a week */
public static ms = 7 * Day.ms;
/** Seconds in a week */
public static seconds = 7 * Day.seconds;
/** Minutes in a week */
public static minutes = 7 * Day.minutes;
/** Days in a week */
public static days = 7;
}