@@ -46,17 +46,21 @@ function createSignature(data: string): string {
46
46
signer . update ( data ) ;
47
47
return normalizeBase64 ( signer . sign ( privateKey , "base64" ) ) ;
48
48
}
49
+
49
50
function verifySignature ( signature : string , data : string ) : boolean {
50
51
const verifier = createVerify ( "RSA-SHA1" ) ;
51
52
verifier . update ( data ) ;
52
53
return verifier . verify ( privateKey , signature , "base64" ) ;
53
54
}
55
+
54
56
function encodeToBase64 ( str : string ) : string {
55
57
return normalizeBase64 ( Buffer . from ( str ) . toString ( "base64" ) ) ;
56
58
}
59
+
57
60
function normalizeBase64 ( str : string ) : string {
58
61
return str . replace ( / \+ / g, "-" ) . replace ( / = / g, "_" ) . replace ( / \/ / g, "~" ) ;
59
62
}
63
+
60
64
function denormalizeBase64 ( str : string ) : string {
61
65
return str . replace ( / \- / g, "+" ) . replace ( / _ / g, "=" ) . replace ( / ~ / g, "/" ) ;
62
66
}
@@ -78,6 +82,7 @@ describe("getSignedUrl", () => {
78
82
}
79
83
expect ( result . query [ "foo" ] ) . toBe ( "bar &=; baz" ) ;
80
84
} ) ;
85
+
81
86
it ( "should include url path in policy of signed URL" , ( ) => {
82
87
const url = "https://example.com/private.jpeg?foo=bar" ;
83
88
const result = parseUrl (
@@ -108,6 +113,7 @@ describe("getSignedUrl", () => {
108
113
} ) ;
109
114
expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
110
115
} ) ;
116
+
111
117
it ( "should sign a URL with a canned policy" , ( ) => {
112
118
const result = getSignedUrl ( {
113
119
url,
@@ -135,6 +141,7 @@ describe("getSignedUrl", () => {
135
141
const signatureQueryParam = denormalizeBase64 ( parsedUrl . query ! [ "Signature" ] as string ) ;
136
142
expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
137
143
} ) ;
144
+
138
145
it ( "should sign a URL with a custom policy containing a start date" , ( ) => {
139
146
const result = getSignedUrl ( {
140
147
url,
@@ -166,6 +173,7 @@ describe("getSignedUrl", () => {
166
173
const signatureQueryParam = denormalizeBase64 ( parsedUrl . query ! [ "Signature" ] as string ) ;
167
174
expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
168
175
} ) ;
176
+
169
177
it ( "should sign a URL with a custom policy containing an ip address" , ( ) => {
170
178
const result = getSignedUrl ( {
171
179
url,
@@ -197,6 +205,7 @@ describe("getSignedUrl", () => {
197
205
const signatureQueryParam = denormalizeBase64 ( parsedUrl . query ! [ "Signature" ] as string ) ;
198
206
expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
199
207
} ) ;
208
+
200
209
it ( "should sign a URL with a custom policy containing a start date and ip address" , ( ) => {
201
210
const result = getSignedUrl ( {
202
211
url,
@@ -232,6 +241,7 @@ describe("getSignedUrl", () => {
232
241
const signatureQueryParam = denormalizeBase64 ( parsedUrl . query ! [ "Signature" ] as string ) ;
233
242
expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
234
243
} ) ;
244
+
235
245
it ( "should allow an ip address with and without a mask" , ( ) => {
236
246
const baseArgs = {
237
247
url,
@@ -253,6 +263,7 @@ describe("getSignedUrl", () => {
253
263
} )
254
264
) . toBeTruthy ( ) ;
255
265
} ) ;
266
+
256
267
it ( "should throw an error when the ip address is invalid" , ( ) => {
257
268
const baseArgs = {
258
269
url,
@@ -298,6 +309,7 @@ describe("getSignedUrl", () => {
298
309
} )
299
310
) . toThrow ( 'IP address "10.0.0.256" is invalid due to invalid IP octets.' ) ;
300
311
} ) ;
312
+
301
313
it ( "should sign a RTMP URL" , ( ) => {
302
314
const url = "rtmp://d111111abcdef8.cloudfront.net/private-content/private.jpeg" ;
303
315
const result = getSignedUrl ( {
@@ -325,6 +337,7 @@ describe("getSignedUrl", () => {
325
337
) ;
326
338
expect ( verifySignature ( denormalizeBase64 ( signature ) , policyStr ) ) . toBeTruthy ( ) ;
327
339
} ) ;
340
+
328
341
it ( "should sign a URL with a policy provided by the user" , ( ) => {
329
342
const policy = '{"foo":"bar"}' ;
330
343
const result = getSignedUrl ( {
@@ -339,6 +352,7 @@ describe("getSignedUrl", () => {
339
352
const signatureQueryParam = denormalizeBase64 ( signature ) ;
340
353
expect ( verifySignature ( signatureQueryParam , policy ) ) . toBeTruthy ( ) ;
341
354
} ) ;
355
+
342
356
it ( "should sign a URL automatically extracted from a policy provided by the user" , ( ) => {
343
357
const policy = JSON . stringify ( { Statement : [ { Resource : url } ] } ) ;
344
358
const result = getSignedUrl ( {
@@ -352,6 +366,23 @@ describe("getSignedUrl", () => {
352
366
const signatureQueryParam = denormalizeBase64 ( signature ) ;
353
367
expect ( verifySignature ( signatureQueryParam , policy ) ) . toBeTruthy ( ) ;
354
368
} ) ;
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
+ } ) ;
355
386
} ) ;
356
387
357
388
describe ( "getSignedCookies" , ( ) => {
@@ -376,6 +407,7 @@ describe("getSignedCookies", () => {
376
407
} )
377
408
) . toBeTruthy ( ) ;
378
409
} ) ;
410
+
379
411
it ( "should throw an error when the ip address is invalid" , ( ) => {
380
412
const baseArgs = {
381
413
url,
@@ -421,6 +453,7 @@ describe("getSignedCookies", () => {
421
453
} )
422
454
) . toThrow ( 'IP address "10.0.0.256" is invalid due to invalid IP octets.' ) ;
423
455
} ) ;
456
+
424
457
it ( "should be able sign cookies that contain a URL with wildcards" , ( ) => {
425
458
const url = "https://example.com/private-content/*" ;
426
459
const result = getSignedCookies ( {
@@ -444,6 +477,7 @@ describe("getSignedCookies", () => {
444
477
} ) ;
445
478
expect ( verifySignature ( denormalizeBase64 ( result [ "CloudFront-Signature" ] ) , policyStr ) ) . toBeTruthy ( ) ;
446
479
} ) ;
480
+
447
481
it ( "should sign cookies with a canned policy" , ( ) => {
448
482
const result = getSignedCookies ( {
449
483
url,
@@ -475,6 +509,7 @@ describe("getSignedCookies", () => {
475
509
expect ( result [ "CloudFront-Signature" ] ) . toBe ( expected [ "CloudFront-Signature" ] ) ;
476
510
expect ( verifySignature ( denormalizeBase64 ( result [ "CloudFront-Signature" ] ) , policyStr ) ) . toBeTruthy ( ) ;
477
511
} ) ;
512
+
478
513
it ( "should sign cookies with a custom policy containing a start date" , ( ) => {
479
514
const result = getSignedCookies ( {
480
515
url,
@@ -510,6 +545,7 @@ describe("getSignedCookies", () => {
510
545
expect ( result [ "CloudFront-Signature" ] ) . toBe ( expected [ "CloudFront-Signature" ] ) ;
511
546
expect ( verifySignature ( denormalizeBase64 ( result [ "CloudFront-Signature" ] ) , policyStr ) ) . toBeTruthy ( ) ;
512
547
} ) ;
548
+
513
549
it ( "should sign cookies with a custom policy containing an ip address" , ( ) => {
514
550
const result = getSignedCookies ( {
515
551
url,
@@ -545,6 +581,7 @@ describe("getSignedCookies", () => {
545
581
expect ( result [ "CloudFront-Signature" ] ) . toBe ( expected [ "CloudFront-Signature" ] ) ;
546
582
expect ( verifySignature ( denormalizeBase64 ( result [ "CloudFront-Signature" ] ) , policyStr ) ) . toBeTruthy ( ) ;
547
583
} ) ;
584
+
548
585
it ( "should sign cookies with a custom policy containing a start date and ip address" , ( ) => {
549
586
const result = getSignedCookies ( {
550
587
url,
@@ -584,6 +621,7 @@ describe("getSignedCookies", () => {
584
621
expect ( result [ "CloudFront-Signature" ] ) . toBe ( expected [ "CloudFront-Signature" ] ) ;
585
622
expect ( verifySignature ( denormalizeBase64 ( result [ "CloudFront-Signature" ] ) , policyStr ) ) . toBeTruthy ( ) ;
586
623
} ) ;
624
+
587
625
it ( "should sign cookies with a policy provided by the user without a url" , ( ) => {
588
626
const policy = '{"foo":"bar"}' ;
589
627
const result = getSignedCookies ( {
@@ -612,6 +650,7 @@ describe("getSignedUrl- when signing a URL with a date range", () => {
612
650
const dateGreaterThanNumber = 1716034245000 ;
613
651
const dateObject = new Date ( dateString ) ;
614
652
const dateGreaterThanObject = new Date ( dateGreaterThanString ) ;
653
+
615
654
it ( "allows string input compatible with Date constructor" , ( ) => {
616
655
const epochDateLessThan = Math . round ( new Date ( dateString ) . getTime ( ) / 1000 ) ;
617
656
const resultUrl = getSignedUrl ( {
@@ -653,6 +692,7 @@ describe("getSignedUrl- when signing a URL with a date range", () => {
653
692
expect ( resultUrl ) . toContain ( `Expires=${ epochDateLessThan } ` ) ;
654
693
expect ( resultCookies [ "CloudFront-Expires" ] ) . toBe ( epochDateLessThan ) ;
655
694
} ) ;
695
+
656
696
it ( "allows Date object input" , ( ) => {
657
697
const epochDateLessThan = Math . round ( dateObject . getTime ( ) / 1000 ) ;
658
698
const resultUrl = getSignedUrl ( {
@@ -673,6 +713,7 @@ describe("getSignedUrl- when signing a URL with a date range", () => {
673
713
expect ( resultUrl ) . toContain ( `Expires=${ epochDateLessThan } ` ) ;
674
714
expect ( resultCookies [ "CloudFront-Expires" ] ) . toBe ( epochDateLessThan ) ;
675
715
} ) ;
716
+
676
717
it ( "allows string input for date range" , ( ) => {
677
718
const result = getSignedUrl ( {
678
719
url,
@@ -736,6 +777,7 @@ describe("getSignedUrl- when signing a URL with a date range", () => {
736
777
const signatureQueryParam = denormalizeBase64 ( parsedUrl . query ! [ "Signature" ] as string ) ;
737
778
expect ( verifySignature ( signatureQueryParam , policyStr ) ) . toBeTruthy ( ) ;
738
779
} ) ;
780
+
739
781
it ( "allows Date object input for date range" , ( ) => {
740
782
const result = getSignedUrl ( {
741
783
url,
0 commit comments