diff --git a/packages/cloudfront-signer/src/sign.spec.ts b/packages/cloudfront-signer/src/sign.spec.ts index 4903525cd843..98c6463ff3ec 100644 --- a/packages/cloudfront-signer/src/sign.spec.ts +++ b/packages/cloudfront-signer/src/sign.spec.ts @@ -604,3 +604,167 @@ describe("getSignedCookies", () => { expect(verifySignature(denormalizeBase64(result["CloudFront-Signature"]), policy)).toBeTruthy(); }); }); + +describe("getSignedUrl- when signing a URL with a date range", () => { + const dateString = "2024-05-17T12:30:45.000Z"; + const dateGreaterThanString = "2024-05-16T12:30:45.000Z"; + const dateNumber = 1125674245900; + const dateGreaterThanNumber = 1716034245000; + const dateObject = new Date(dateString); + const dateGreaterThanObject = new Date(dateGreaterThanString); + it("allows string input compatible with Date constructor", () => { + const epochDateLessThan = Math.round(new Date(dateString).getTime() / 1000); + const resultUrl = getSignedUrl({ + url, + keyPairId, + dateLessThan: dateString, + privateKey, + passphrase, + }); + const resultCookies = getSignedCookies({ + url, + keyPairId, + dateLessThan: dateString, + privateKey, + passphrase, + }); + + expect(resultUrl).toContain(`Expires=${epochDateLessThan}`); + expect(resultCookies["CloudFront-Expires"]).toBe(epochDateLessThan); + }); + + it("allows number input in milliseconds compatible with Date constructor", () => { + const epochDateLessThan = Math.round(new Date(dateNumber).getTime() / 1000); + const resultUrl = getSignedUrl({ + url, + keyPairId, + dateLessThan: dateNumber, + privateKey, + passphrase, + }); + const resultCookies = getSignedCookies({ + url, + keyPairId, + dateLessThan: dateNumber, + privateKey, + passphrase, + }); + + expect(resultUrl).toContain(`Expires=${epochDateLessThan}`); + expect(resultCookies["CloudFront-Expires"]).toBe(epochDateLessThan); + }); + it("allows Date object input", () => { + const epochDateLessThan = Math.round(dateObject.getTime() / 1000); + const resultUrl = getSignedUrl({ + url, + keyPairId, + dateLessThan: dateObject, + privateKey, + passphrase, + }); + const resultCookies = getSignedCookies({ + url, + keyPairId, + dateLessThan: dateObject, + privateKey, + passphrase, + }); + + expect(resultUrl).toContain(`Expires=${epochDateLessThan}`); + expect(resultCookies["CloudFront-Expires"]).toBe(epochDateLessThan); + }); + it("allows string input for date range", () => { + const result = getSignedUrl({ + url, + keyPairId, + dateLessThan: dateString, + dateGreaterThan: dateGreaterThanString, + privateKey, + passphrase, + }); + + const policyStr = JSON.stringify({ + Statement: [ + { + Resource: url, + Condition: { + DateLessThan: { + "AWS:EpochTime": Math.round(new Date(dateString).getTime() / 1000), + }, + DateGreaterThan: { + "AWS:EpochTime": Math.round(new Date(dateGreaterThanString).getTime() / 1000), + }, + }, + }, + ], + }); + + const parsedUrl = parseUrl(result); + expect(parsedUrl).toBeDefined(); + const signatureQueryParam = denormalizeBase64(parsedUrl.query!["Signature"] as string); + expect(verifySignature(signatureQueryParam, policyStr)).toBeTruthy(); + }); + + it("allows number input for date range", () => { + const result = getSignedUrl({ + url, + keyPairId, + dateLessThan: dateNumber, + dateGreaterThan: dateGreaterThanNumber, + privateKey, + passphrase, + }); + + const policyStr = JSON.stringify({ + Statement: [ + { + Resource: url, + Condition: { + DateLessThan: { + "AWS:EpochTime": Math.round(dateNumber / 1000), + }, + DateGreaterThan: { + "AWS:EpochTime": Math.round(dateGreaterThanNumber / 1000), + }, + }, + }, + ], + }); + + const parsedUrl = parseUrl(result); + expect(parsedUrl).toBeDefined(); + const signatureQueryParam = denormalizeBase64(parsedUrl.query!["Signature"] as string); + expect(verifySignature(signatureQueryParam, policyStr)).toBeTruthy(); + }); + it("allows Date object input for date range", () => { + const result = getSignedUrl({ + url, + keyPairId, + dateLessThan: dateObject, + dateGreaterThan: dateGreaterThanObject, + privateKey, + passphrase, + }); + + const policyStr = JSON.stringify({ + Statement: [ + { + Resource: url, + Condition: { + DateLessThan: { + "AWS:EpochTime": Math.round(dateObject.getTime() / 1000), + }, + DateGreaterThan: { + "AWS:EpochTime": Math.round(dateGreaterThanObject.getTime() / 1000), + }, + }, + }, + ], + }); + + const parsedUrl = parseUrl(result); + expect(parsedUrl).toBeDefined(); + const signatureQueryParam = denormalizeBase64(parsedUrl.query!["Signature"] as string); + expect(verifySignature(signatureQueryParam, policyStr)).toBeTruthy(); + }); +}); diff --git a/packages/cloudfront-signer/src/sign.ts b/packages/cloudfront-signer/src/sign.ts index 66ad12a5f8d8..a61fd34ea7c3 100644 --- a/packages/cloudfront-signer/src/sign.ts +++ b/packages/cloudfront-signer/src/sign.ts @@ -25,9 +25,9 @@ export type CloudfrontSignInputWithParameters = CloudfrontSignerCredentials & { /** The URL string to sign. */ url: string; /** The date string for when the signed URL or cookie can no longer be accessed */ - dateLessThan: string; + dateLessThan: string | number | Date; /** The date string for when the signed URL or cookie can start to be accessed. */ - dateGreaterThan?: string; + dateGreaterThan?: string | number | Date; /** The IP address string to restrict signed URL access to. */ ipAddress?: string; /** @@ -359,18 +359,18 @@ class CloudfrontSignBuilder { return Math.round(date.getTime() / 1000); } - private parseDate(date?: string): number | undefined { + private parseDate(date?: string | number | Date): number | undefined { if (!date) { return undefined; } - const parsedDate = Date.parse(date); - return isNaN(parsedDate) ? undefined : this.epochTime(new Date(parsedDate)); + const parsedDate = new Date(date); + return isNaN(parsedDate.getTime()) ? undefined : this.epochTime(parsedDate); } - private parseDateWindow(expiration: string, start?: string): PolicyDates { + private parseDateWindow(expiration: string | number | Date, start?: string | number | Date): PolicyDates { const dateLessThan = this.parseDate(expiration); if (!dateLessThan) { - throw new Error("dateLessThan is invalid. Ensure the date string is compatible with the Date constructor."); + throw new Error("dateLessThan is invalid. Ensure the date value is compatible with the Date constructor."); } return { dateLessThan, @@ -400,8 +400,8 @@ class CloudfrontSignBuilder { ipAddress, }: { url?: string; - dateLessThan?: string; - dateGreaterThan?: string; + dateLessThan?: string | number | Date; + dateGreaterThan?: string | number | Date; ipAddress?: string; }) { if (!url || !dateLessThan) {