@@ -46,17 +46,21 @@ function createSignature(data: string): string {
4646 signer . update ( data ) ;
4747 return normalizeBase64 ( signer . sign ( privateKey , "base64" ) ) ;
4848}
49+
4950function verifySignature ( signature : string , data : string ) : boolean {
5051 const verifier = createVerify ( "RSA-SHA1" ) ;
5152 verifier . update ( data ) ;
5253 return verifier . verify ( privateKey , signature , "base64" ) ;
5354}
55+
5456function encodeToBase64 ( str : string ) : string {
5557 return normalizeBase64 ( Buffer . from ( str ) . toString ( "base64" ) ) ;
5658}
59+
5760function normalizeBase64 ( str : string ) : string {
5861 return str . replace ( / \+ / g, "-" ) . replace ( / = / g, "_" ) . replace ( / \/ / g, "~" ) ;
5962}
63+
6064function denormalizeBase64 ( str : string ) : string {
6165 return str . replace ( / \- / g, "+" ) . replace ( / _ / g, "=" ) . replace ( / ~ / g, "/" ) ;
6266}
@@ -78,6 +82,7 @@ describe("getSignedUrl", () => {
7882 }
7983 expect ( result . query [ "foo" ] ) . toBe ( "bar &=; baz" ) ;
8084 } ) ;
85+
8186 it ( "should include url path in policy of signed URL" , ( ) => {
8287 const url = "https://example.com/private.jpeg?foo=bar" ;
8388 const result = parseUrl (
@@ -108,6 +113,7 @@ describe("getSignedUrl", () => {
108113 } ) ;
109114 expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
110115 } ) ;
116+
111117 it ( "should sign a URL with a canned policy" , ( ) => {
112118 const result = getSignedUrl ( {
113119 url,
@@ -135,6 +141,7 @@ describe("getSignedUrl", () => {
135141 const signatureQueryParam = denormalizeBase64 ( parsedUrl . query ! [ "Signature" ] as string ) ;
136142 expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
137143 } ) ;
144+
138145 it ( "should sign a URL with a custom policy containing a start date" , ( ) => {
139146 const result = getSignedUrl ( {
140147 url,
@@ -166,6 +173,7 @@ describe("getSignedUrl", () => {
166173 const signatureQueryParam = denormalizeBase64 ( parsedUrl . query ! [ "Signature" ] as string ) ;
167174 expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
168175 } ) ;
176+
169177 it ( "should sign a URL with a custom policy containing an ip address" , ( ) => {
170178 const result = getSignedUrl ( {
171179 url,
@@ -197,6 +205,7 @@ describe("getSignedUrl", () => {
197205 const signatureQueryParam = denormalizeBase64 ( parsedUrl . query ! [ "Signature" ] as string ) ;
198206 expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
199207 } ) ;
208+
200209 it ( "should sign a URL with a custom policy containing a start date and ip address" , ( ) => {
201210 const result = getSignedUrl ( {
202211 url,
@@ -232,6 +241,7 @@ describe("getSignedUrl", () => {
232241 const signatureQueryParam = denormalizeBase64 ( parsedUrl . query ! [ "Signature" ] as string ) ;
233242 expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
234243 } ) ;
244+
235245 it ( "should allow an ip address with and without a mask" , ( ) => {
236246 const baseArgs = {
237247 url,
@@ -253,6 +263,7 @@ describe("getSignedUrl", () => {
253263 } )
254264 ) . toBeTruthy ( ) ;
255265 } ) ;
266+
256267 it ( "should throw an error when the ip address is invalid" , ( ) => {
257268 const baseArgs = {
258269 url,
@@ -298,6 +309,7 @@ describe("getSignedUrl", () => {
298309 } )
299310 ) . toThrow ( 'IP address "10.0.0.256" is invalid due to invalid IP octets.' ) ;
300311 } ) ;
312+
301313 it ( "should sign a RTMP URL" , ( ) => {
302314 const url = "rtmp://d111111abcdef8.cloudfront.net/private-content/private.jpeg" ;
303315 const result = getSignedUrl ( {
@@ -325,6 +337,7 @@ describe("getSignedUrl", () => {
325337 ) ;
326338 expect ( verifySignature ( denormalizeBase64 ( signature ) , policyStr ) ) . toBeTruthy ( ) ;
327339 } ) ;
340+
328341 it ( "should sign a URL with a policy provided by the user" , ( ) => {
329342 const policy = '{"foo":"bar"}' ;
330343 const result = getSignedUrl ( {
@@ -339,6 +352,7 @@ describe("getSignedUrl", () => {
339352 const signatureQueryParam = denormalizeBase64 ( signature ) ;
340353 expect ( verifySignature ( signatureQueryParam , policy ) ) . toBeTruthy ( ) ;
341354 } ) ;
355+
342356 it ( "should sign a URL automatically extracted from a policy provided by the user" , ( ) => {
343357 const policy = JSON . stringify ( { Statement : [ { Resource : url } ] } ) ;
344358 const result = getSignedUrl ( {
@@ -352,6 +366,23 @@ describe("getSignedUrl", () => {
352366 const signatureQueryParam = denormalizeBase64 ( signature ) ;
353367 expect ( verifySignature ( signatureQueryParam , policy ) ) . toBeTruthy ( ) ;
354368 } ) ;
369+
370+ describe ( "should not normalize the URL" , ( ) => {
371+ it . each ( [ "." , ".." ] ) ( "with '%s'" , ( folderName ) => {
372+ const urlWithFolderName = `https://d111111abcdef8.cloudfront.net/public-content/${ folderName } /private-content/private.jpeg` ;
373+ const policy = JSON . stringify ( { Statement : [ { Resource : urlWithFolderName } ] } ) ;
374+ const result = getSignedUrl ( {
375+ keyPairId,
376+ privateKey,
377+ policy,
378+ passphrase,
379+ } ) ;
380+ const signature = createSignature ( policy ) ;
381+ expect ( result . startsWith ( urlWithFolderName ) ) . toBeTruthy ( ) ;
382+ const signatureQueryParam = denormalizeBase64 ( signature ) ;
383+ expect ( verifySignature ( signatureQueryParam , policy ) ) . toBeTruthy ( ) ;
384+ } ) ;
385+ } ) ;
355386} ) ;
356387
357388describe ( "getSignedCookies" , ( ) => {
@@ -376,6 +407,7 @@ describe("getSignedCookies", () => {
376407 } )
377408 ) . toBeTruthy ( ) ;
378409 } ) ;
410+
379411 it ( "should throw an error when the ip address is invalid" , ( ) => {
380412 const baseArgs = {
381413 url,
@@ -421,6 +453,7 @@ describe("getSignedCookies", () => {
421453 } )
422454 ) . toThrow ( 'IP address "10.0.0.256" is invalid due to invalid IP octets.' ) ;
423455 } ) ;
456+
424457 it ( "should be able sign cookies that contain a URL with wildcards" , ( ) => {
425458 const url = "https://example.com/private-content/*" ;
426459 const result = getSignedCookies ( {
@@ -444,6 +477,7 @@ describe("getSignedCookies", () => {
444477 } ) ;
445478 expect ( verifySignature ( denormalizeBase64 ( result [ "CloudFront-Signature" ] ) , policyStr ) ) . toBeTruthy ( ) ;
446479 } ) ;
480+
447481 it ( "should sign cookies with a canned policy" , ( ) => {
448482 const result = getSignedCookies ( {
449483 url,
@@ -475,6 +509,7 @@ describe("getSignedCookies", () => {
475509 expect ( result [ "CloudFront-Signature" ] ) . toBe ( expected [ "CloudFront-Signature" ] ) ;
476510 expect ( verifySignature ( denormalizeBase64 ( result [ "CloudFront-Signature" ] ) , policyStr ) ) . toBeTruthy ( ) ;
477511 } ) ;
512+
478513 it ( "should sign cookies with a custom policy containing a start date" , ( ) => {
479514 const result = getSignedCookies ( {
480515 url,
@@ -510,6 +545,7 @@ describe("getSignedCookies", () => {
510545 expect ( result [ "CloudFront-Signature" ] ) . toBe ( expected [ "CloudFront-Signature" ] ) ;
511546 expect ( verifySignature ( denormalizeBase64 ( result [ "CloudFront-Signature" ] ) , policyStr ) ) . toBeTruthy ( ) ;
512547 } ) ;
548+
513549 it ( "should sign cookies with a custom policy containing an ip address" , ( ) => {
514550 const result = getSignedCookies ( {
515551 url,
@@ -545,6 +581,7 @@ describe("getSignedCookies", () => {
545581 expect ( result [ "CloudFront-Signature" ] ) . toBe ( expected [ "CloudFront-Signature" ] ) ;
546582 expect ( verifySignature ( denormalizeBase64 ( result [ "CloudFront-Signature" ] ) , policyStr ) ) . toBeTruthy ( ) ;
547583 } ) ;
584+
548585 it ( "should sign cookies with a custom policy containing a start date and ip address" , ( ) => {
549586 const result = getSignedCookies ( {
550587 url,
@@ -584,6 +621,7 @@ describe("getSignedCookies", () => {
584621 expect ( result [ "CloudFront-Signature" ] ) . toBe ( expected [ "CloudFront-Signature" ] ) ;
585622 expect ( verifySignature ( denormalizeBase64 ( result [ "CloudFront-Signature" ] ) , policyStr ) ) . toBeTruthy ( ) ;
586623 } ) ;
624+
587625 it ( "should sign cookies with a policy provided by the user without a url" , ( ) => {
588626 const policy = '{"foo":"bar"}' ;
589627 const result = getSignedCookies ( {
@@ -612,6 +650,7 @@ describe("getSignedUrl- when signing a URL with a date range", () => {
612650 const dateGreaterThanNumber = 1716034245000 ;
613651 const dateObject = new Date ( dateString ) ;
614652 const dateGreaterThanObject = new Date ( dateGreaterThanString ) ;
653+
615654 it ( "allows string input compatible with Date constructor" , ( ) => {
616655 const epochDateLessThan = Math . round ( new Date ( dateString ) . getTime ( ) / 1000 ) ;
617656 const resultUrl = getSignedUrl ( {
@@ -653,6 +692,7 @@ describe("getSignedUrl- when signing a URL with a date range", () => {
653692 expect ( resultUrl ) . toContain ( `Expires=${ epochDateLessThan } ` ) ;
654693 expect ( resultCookies [ "CloudFront-Expires" ] ) . toBe ( epochDateLessThan ) ;
655694 } ) ;
695+
656696 it ( "allows Date object input" , ( ) => {
657697 const epochDateLessThan = Math . round ( dateObject . getTime ( ) / 1000 ) ;
658698 const resultUrl = getSignedUrl ( {
@@ -673,6 +713,7 @@ describe("getSignedUrl- when signing a URL with a date range", () => {
673713 expect ( resultUrl ) . toContain ( `Expires=${ epochDateLessThan } ` ) ;
674714 expect ( resultCookies [ "CloudFront-Expires" ] ) . toBe ( epochDateLessThan ) ;
675715 } ) ;
716+
676717 it ( "allows string input for date range" , ( ) => {
677718 const result = getSignedUrl ( {
678719 url,
@@ -736,6 +777,7 @@ describe("getSignedUrl- when signing a URL with a date range", () => {
736777 const signatureQueryParam = denormalizeBase64 ( parsedUrl . query ! [ "Signature" ] as string ) ;
737778 expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
738779 } ) ;
780+
739781 it ( "allows Date object input for date range" , ( ) => {
740782 const result = getSignedUrl ( {
741783 url,
0 commit comments