mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
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:
@@ -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"));
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user