@@ -16,13 +16,18 @@ limitations under the License.
16
16
17
17
import { decodeBase64 } from "../../src/base64" ;
18
18
import {
19
- randomLowercaseString ,
20
- randomString ,
21
- randomUppercaseString ,
19
+ secureRandomString ,
22
20
secureRandomBase64Url ,
21
+ secureRandomStringFrom ,
22
+ LOWERCASE ,
23
+ UPPERCASE ,
23
24
} from "../../src/randomstring" ;
24
25
25
26
describe ( "Random strings" , ( ) => {
27
+ afterEach ( ( ) => {
28
+ jest . restoreAllMocks ( ) ;
29
+ } ) ;
30
+
26
31
it . each ( [ 8 , 16 , 32 ] ) ( "secureRandomBase64 generates %i valid base64 bytes" , ( n : number ) => {
27
32
const randb641 = secureRandomBase64Url ( n ) ;
28
33
const randb642 = secureRandomBase64Url ( n ) ;
@@ -33,34 +38,77 @@ describe("Random strings", () => {
33
38
expect ( decoded ) . toHaveLength ( n ) ;
34
39
} ) ;
35
40
36
- it . each ( [ 8 , 16 , 32 ] ) ( "randomString generates string of %i characters" , ( n : number ) => {
37
- const rand1 = randomString ( n ) ;
38
- const rand2 = randomString ( n ) ;
41
+ it . each ( [ 8 , 16 , 32 ] ) ( "secureRandomString generates string of %i characters" , ( n : number ) => {
42
+ const rand1 = secureRandomString ( n ) ;
43
+ const rand2 = secureRandomString ( n ) ;
39
44
40
45
expect ( rand1 ) . not . toEqual ( rand2 ) ;
41
46
42
47
expect ( rand1 ) . toHaveLength ( n ) ;
43
48
} ) ;
44
49
45
- it . each ( [ 8 , 16 , 32 ] ) ( "randomLowercaseString generates lowercase string of %i characters" , ( n : number ) => {
46
- const rand1 = randomLowercaseString ( n ) ;
47
- const rand2 = randomLowercaseString ( n ) ;
50
+ it . each ( [ 8 , 16 , 32 ] ) (
51
+ "secureRandomStringFrom generates lowercase string of %i characters when given lowercase" ,
52
+ ( n : number ) => {
53
+ const rand1 = secureRandomStringFrom ( n , LOWERCASE ) ;
54
+ const rand2 = secureRandomStringFrom ( n , LOWERCASE ) ;
48
55
49
- expect ( rand1 ) . not . toEqual ( rand2 ) ;
56
+ expect ( rand1 ) . not . toEqual ( rand2 ) ;
50
57
51
- expect ( rand1 ) . toHaveLength ( n ) ;
58
+ expect ( rand1 ) . toHaveLength ( n ) ;
59
+
60
+ expect ( rand1 . toLowerCase ( ) ) . toEqual ( rand1 ) ;
61
+ } ,
62
+ ) ;
63
+
64
+ it . each ( [ 8 , 16 , 32 ] ) (
65
+ "secureRandomStringFrom generates uppercase string of %i characters when given uppercase" ,
66
+ ( n : number ) => {
67
+ const rand1 = secureRandomStringFrom ( n , UPPERCASE ) ;
68
+ const rand2 = secureRandomStringFrom ( n , UPPERCASE ) ;
69
+
70
+ expect ( rand1 ) . not . toEqual ( rand2 ) ;
71
+
72
+ expect ( rand1 ) . toHaveLength ( n ) ;
52
73
53
- expect ( rand1 . toLowerCase ( ) ) . toEqual ( rand1 ) ;
74
+ expect ( rand1 . toUpperCase ( ) ) . toEqual ( rand1 ) ;
75
+ } ,
76
+ ) ;
77
+
78
+ it ( "throws if given character set less than 2 characters" , ( ) => {
79
+ expect ( ( ) => secureRandomStringFrom ( 8 , "a" ) ) . toThrow ( ) ;
54
80
} ) ;
55
81
56
- it . each ( [ 8 , 16 , 32 ] ) ( "randomUppercaseString generates lowercase string of %i characters" , ( n : number ) => {
57
- const rand1 = randomUppercaseString ( n ) ;
58
- const rand2 = randomUppercaseString ( n ) ;
82
+ it ( "throws if given character set more than 256 characters" , ( ) => {
83
+ const charSet = Array . from ( { length : 257 } , ( _ , i ) => "a" ) . join ( "" ) ;
59
84
60
- expect ( rand1 ) . not . toEqual ( rand2 ) ;
85
+ expect ( ( ) => secureRandomStringFrom ( 8 , charSet ) ) . toThrow ( ) ;
86
+ } ) ;
61
87
62
- expect ( rand1 ) . toHaveLength ( n ) ;
88
+ it ( "throws if given length less than 1" , ( ) => {
89
+ expect ( ( ) => secureRandomStringFrom ( 0 , "abc" ) ) . toThrow ( ) ;
90
+ } ) ;
91
+
92
+ it ( "throws if given length more than 32768" , ( ) => {
93
+ expect ( ( ) => secureRandomStringFrom ( 32769 , "abc" ) ) . toThrow ( ) ;
94
+ } ) ;
63
95
64
- expect ( rand1 . toUpperCase ( ) ) . toEqual ( rand1 ) ;
96
+ it ( "asks for more entropy if given entropy is unusable" , ( ) => {
97
+ // This is testing the internal implementation details of the function rather
98
+ // than strictly the public API. The intention is to have some assertion that
99
+ // the rejection sampling to make the distribution even over all possible characters
100
+ // is doing what it's supposed to do.
101
+
102
+ // mock once to fill with 255 the first time: 255 should be unusable because
103
+ // we give 10 possible characters below and 256 is not evenly divisible by 10, so
104
+ // this should force it to call for more entropy.
105
+ jest . spyOn ( globalThis . crypto , "getRandomValues" ) . mockImplementationOnce ( ( arr ) => {
106
+ if ( arr === null ) throw new Error ( "Buffer is null" ) ;
107
+ new Uint8Array ( arr . buffer ) . fill ( 255 ) ;
108
+ return arr ;
109
+ } ) ;
110
+
111
+ secureRandomStringFrom ( 8 , "0123456789" ) ;
112
+ expect ( globalThis . crypto . getRandomValues ) . toHaveBeenCalledTimes ( 2 ) ;
65
113
} ) ;
66
114
} ) ;
0 commit comments