@@ -38,4 +38,218 @@ describeMatrix("auth", (t, { it, expect }) => {
3838 expect ( await result . text ( ) ) . toBe ( "Hello, world!" ) ;
3939 expect ( result . status ) . toBe ( 200 ) ;
4040 } ) ;
41+
42+ it ( "handles password containing colons" , async ( ) => {
43+ const authWithColon = basicAuth ( {
44+ username : "admin" ,
45+ password : "pass:word:with:colons" ,
46+ } ) ;
47+ t . app . get ( "/colon-test" , ( ) => "Success!" , { middleware : [ authWithColon ] } ) ;
48+
49+ const result = await t . fetch ( "/colon-test" , {
50+ method : "GET" ,
51+ headers : {
52+ Authorization : `Basic ${ Buffer . from ( "admin:pass:word:with:colons" ) . toString ( "base64" ) } ` ,
53+ } ,
54+ } ) ;
55+
56+ expect ( await result . text ( ) ) . toBe ( "Success!" ) ;
57+ expect ( result . status ) . toBe ( 200 ) ;
58+ } ) ;
59+
60+ it ( "rejects wrong password when password contains colons" , async ( ) => {
61+ const authWithColon = basicAuth ( {
62+ username : "admin" ,
63+ password : "pass:word:with:colons" ,
64+ } ) ;
65+ t . app . get ( "/colon-reject" , ( ) => "Success!" , {
66+ middleware : [ authWithColon ] ,
67+ } ) ;
68+
69+ const result = await t . fetch ( "/colon-reject" , {
70+ method : "GET" ,
71+ headers : {
72+ Authorization : `Basic ${ Buffer . from ( "admin:pass:word" ) . toString ( "base64" ) } ` ,
73+ } ,
74+ } ) ;
75+
76+ expect ( result . status ) . toBe ( 401 ) ;
77+ } ) ;
78+
79+ it ( "responds 401 for invalid base64" , async ( ) => {
80+ t . app . get ( "/invalid-base64" , ( ) => "Hello, world!" , { middleware : [ auth ] } ) ;
81+ const result = await t . fetch ( "/invalid-base64" , {
82+ method : "GET" ,
83+ headers : {
84+ Authorization : "Basic !!!invalid-base64!!!" ,
85+ } ,
86+ } ) ;
87+
88+ expect ( result . status ) . toBe ( 401 ) ;
89+ } ) ;
90+
91+ it ( "responds 401 when base64 value has no colon separator" , async ( ) => {
92+ t . app . get ( "/no-colon" , ( ) => "Hello, world!" , { middleware : [ auth ] } ) ;
93+ const result = await t . fetch ( "/no-colon" , {
94+ method : "GET" ,
95+ headers : {
96+ Authorization : `Basic ${ Buffer . from ( "usernameonly" ) . toString ( "base64" ) } ` ,
97+ } ,
98+ } ) ;
99+
100+ expect ( result . status ) . toBe ( 401 ) ;
101+ } ) ;
102+
103+ it ( "responds 401 when username is empty" , async ( ) => {
104+ t . app . get ( "/empty-username" , ( ) => "Hello, world!" , { middleware : [ auth ] } ) ;
105+ const result = await t . fetch ( "/empty-username" , {
106+ method : "GET" ,
107+ headers : {
108+ Authorization : `Basic ${ Buffer . from ( ":password" ) . toString ( "base64" ) } ` ,
109+ } ,
110+ } ) ;
111+
112+ expect ( result . status ) . toBe ( 401 ) ;
113+ } ) ;
114+
115+ it ( "responds 401 when password is empty" , async ( ) => {
116+ t . app . get ( "/empty-password" , ( ) => "Hello, world!" , { middleware : [ auth ] } ) ;
117+ const result = await t . fetch ( "/empty-password" , {
118+ method : "GET" ,
119+ headers : {
120+ Authorization : `Basic ${ Buffer . from ( "username:" ) . toString ( "base64" ) } ` ,
121+ } ,
122+ } ) ;
123+
124+ expect ( result . status ) . toBe ( 401 ) ;
125+ } ) ;
126+
127+ it ( "responds 401 when both username and password are empty" , async ( ) => {
128+ t . app . get ( "/empty-both" , ( ) => "Hello, world!" , { middleware : [ auth ] } ) ;
129+ const result = await t . fetch ( "/empty-both" , {
130+ method : "GET" ,
131+ headers : {
132+ Authorization : `Basic ${ Buffer . from ( ":" ) . toString ( "base64" ) } ` ,
133+ } ,
134+ } ) ;
135+
136+ expect ( result . status ) . toBe ( 401 ) ;
137+ } ) ;
138+
139+ it ( "responds 401 when auth type is not Basic" , async ( ) => {
140+ t . app . get ( "/wrong-type" , ( ) => "Hello, world!" , { middleware : [ auth ] } ) ;
141+ const result = await t . fetch ( "/wrong-type" , {
142+ method : "GET" ,
143+ headers : {
144+ Authorization : "Bearer some-token" ,
145+ } ,
146+ } ) ;
147+
148+ expect ( result . status ) . toBe ( 401 ) ;
149+ } ) ;
150+
151+ it ( "responds 401 when auth header has no credentials part" , async ( ) => {
152+ t . app . get ( "/no-credentials" , ( ) => "Hello, world!" , {
153+ middleware : [ auth ] ,
154+ } ) ;
155+ const result = await t . fetch ( "/no-credentials" , {
156+ method : "GET" ,
157+ headers : {
158+ Authorization : "Basic" ,
159+ } ,
160+ } ) ;
161+
162+ expect ( result . status ) . toBe ( 401 ) ;
163+ } ) ;
164+
165+ it ( "responds 401 when auth header is empty" , async ( ) => {
166+ t . app . get ( "/empty-header" , ( ) => "Hello, world!" , { middleware : [ auth ] } ) ;
167+ const result = await t . fetch ( "/empty-header" , {
168+ method : "GET" ,
169+ headers : {
170+ Authorization : "" ,
171+ } ,
172+ } ) ;
173+
174+ expect ( result . status ) . toBe ( 401 ) ;
175+ } ) ;
176+
177+ it ( "supports custom validate function" , async ( ) => {
178+ const customAuth = basicAuth ( {
179+ validate : ( username , password ) => {
180+ return username === "custom" && password === "secret" ;
181+ } ,
182+ } ) ;
183+ t . app . get ( "/custom-validate" , ( ) => "Custom validated!" , {
184+ middleware : [ customAuth ] ,
185+ } ) ;
186+
187+ const result = await t . fetch ( "/custom-validate" , {
188+ method : "GET" ,
189+ headers : {
190+ Authorization : `Basic ${ Buffer . from ( "custom:secret" ) . toString ( "base64" ) } ` ,
191+ } ,
192+ } ) ;
193+
194+ expect ( await result . text ( ) ) . toBe ( "Custom validated!" ) ;
195+ expect ( result . status ) . toBe ( 200 ) ;
196+ } ) ;
197+
198+ it ( "rejects invalid credentials with custom validate function" , async ( ) => {
199+ const customAuth = basicAuth ( {
200+ validate : ( username , password ) => {
201+ return username === "custom" && password === "secret" ;
202+ } ,
203+ } ) ;
204+ t . app . get ( "/custom-validate-reject" , ( ) => "Custom validated!" , {
205+ middleware : [ customAuth ] ,
206+ } ) ;
207+
208+ const result = await t . fetch ( "/custom-validate-reject" , {
209+ method : "GET" ,
210+ headers : {
211+ Authorization : `Basic ${ Buffer . from ( "custom:wrong" ) . toString ( "base64" ) } ` ,
212+ } ,
213+ } ) ;
214+
215+ expect ( result . status ) . toBe ( 401 ) ;
216+ } ) ;
217+
218+ it ( "supports async custom validate function" , async ( ) => {
219+ const asyncAuth = basicAuth ( {
220+ validate : async ( username , password ) => {
221+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
222+ return username === "async" && password === "pass" ;
223+ } ,
224+ } ) ;
225+ t . app . get ( "/async-validate" , ( ) => "Async validated!" , {
226+ middleware : [ asyncAuth ] ,
227+ } ) ;
228+
229+ const result = await t . fetch ( "/async-validate" , {
230+ method : "GET" ,
231+ headers : {
232+ Authorization : `Basic ${ Buffer . from ( "async:pass" ) . toString ( "base64" ) } ` ,
233+ } ,
234+ } ) ;
235+
236+ expect ( await result . text ( ) ) . toBe ( "Async validated!" ) ;
237+ expect ( result . status ) . toBe ( 200 ) ;
238+ } ) ;
239+
240+ it ( "throws error when neither password nor validate is provided" , async ( ) => {
241+ const invalidAuth = basicAuth ( { } as any ) ;
242+ t . app . get ( "/no-auth-config" , ( ) => "Should not reach!" , {
243+ middleware : [ invalidAuth ] ,
244+ } ) ;
245+
246+ const result = await t . fetch ( "/no-auth-config" , {
247+ method : "GET" ,
248+ headers : {
249+ Authorization : `Basic ${ Buffer . from ( "user:pass" ) . toString ( "base64" ) } ` ,
250+ } ,
251+ } ) ;
252+
253+ expect ( result . status ) . toBe ( 500 ) ;
254+ } ) ;
41255} ) ;
0 commit comments