@@ -6,6 +6,7 @@ use rocket::{
66 http:: { ContentType , Status } ,
77 local:: asynchronous:: Client ,
88} ;
9+ use rust_photoacoustic:: config:: AccessConfig ;
910use serde_json:: Value ;
1011use sha2:: { Digest , Sha256 } ;
1112use std:: collections:: HashMap ;
@@ -55,15 +56,26 @@ fn extract_params_from_url(url: &str) -> HashMap<String, String> {
5556
5657#[ rocket:: async_test]
5758async fn test_oauth2_pkce_flow ( ) {
58- // Configure the Rocket test client with a test HMAC secret
59+ // Initialize the logger for tests
60+ let _ = env_logger:: builder ( )
61+ . is_test ( true )
62+ . filter_level ( log:: LevelFilter :: Debug )
63+ . try_init ( ) ;
64+
65+ // Test HMAC secret
5966 let test_hmac_secret = "test-hmac-secret-key-for-testing" ;
60- // Add shutdown configuration to ensure proper test cleanup
67+
68+ // Test AccessConfig with default admin user
69+ let test_access_config = AccessConfig :: default ( ) ;
70+
71+ // Configure the Rocket test client with additional configuration
6172 let figment = get_figment ( )
6273 . merge ( ( "shutdown.ctrlc" , false ) )
6374 . merge ( ( "shutdown.grace" , 1 ) )
6475 . merge ( ( "shutdown.mercy" , 1 ) )
6576 . merge ( ( "shutdown.force" , true ) )
66- . merge ( ( "hmac_secret" , test_hmac_secret) ) ;
77+ . merge ( ( "hmac_secret" , test_hmac_secret) )
78+ . merge ( ( "access" , test_access_config) ) ; // Add access config
6779
6880 let rocket = rust_photoacoustic:: visualization:: server:: build_rocket ( figment) . await ;
6981 let client = Client :: tracked ( rocket)
@@ -92,37 +104,98 @@ async fn test_oauth2_pkce_flow() {
92104 assert ! ( response. content_type( ) . unwrap( ) . is_html( ) ) ;
93105
94106 let body = response. into_string ( ) . await . expect ( "Response body" ) ;
107+
108+ // Should now receive a login form instead of consent form
109+ assert ! (
110+ body. contains( "Username" ) ,
111+ "The login page should contain a Username field"
112+ ) ;
113+ assert ! (
114+ body. contains( "Password" ) ,
115+ "The login page should contain a Password field"
116+ ) ;
117+ assert ! (
118+ body. contains( "Login" ) ,
119+ "The login page should contain a Login button"
120+ ) ;
121+
122+ // Step 2: Extract form action and submit login credentials
123+ println ! ( "Submitting login credentials..." ) ;
124+
125+ // Submit login form with default admin credentials
126+ let mut form_data = HashMap :: new ( ) ;
127+ form_data. insert ( "username" , "admin" ) ;
128+ form_data. insert ( "password" , "admin123" ) ;
129+ form_data. insert ( "response_type" , "code" ) ;
130+ form_data. insert ( "client_id" , "LaserSmartClient" ) ;
131+ form_data. insert ( "redirect_uri" , "http://localhost:8080/client/" ) ;
132+ form_data. insert ( "scope" , "openid profile email read:api write:api" ) ;
133+ form_data. insert ( "state" , "test-state" ) ;
134+
135+ let login_response = client
136+ . post ( "/login" )
137+ . header ( ContentType :: Form )
138+ . body ( serde_urlencoded:: to_string ( & form_data) . unwrap ( ) )
139+ . dispatch ( )
140+ . await ;
141+
142+ // Should redirect back to /authorize with session established
143+ assert_eq ! ( login_response. status( ) , Status :: Found ) ;
144+
145+ let redirect_location = login_response
146+ . headers ( )
147+ . get_one ( "Location" )
148+ . expect ( "Should have location header after login" ) ;
149+
150+ println ! ( "Login redirect location: {}" , redirect_location) ;
151+
152+ // Step 3: Follow redirect to get consent page
153+ let consent_response = client. get ( redirect_location) . dispatch ( ) . await ;
154+
155+ assert_eq ! ( consent_response. status( ) , Status :: Ok ) ;
156+ assert ! ( consent_response. content_type( ) . unwrap( ) . is_html( ) ) ;
157+
158+ let consent_body = consent_response. into_string ( ) . await . expect ( "Consent page body" ) ;
95159 assert ! (
96- body . contains( "Accept" ) ,
160+ consent_body . contains( "Accept" ) ,
97161 "The consent page should contain an Accept button"
98162 ) ;
99163 assert ! (
100- body . contains( "Deny" ) ,
164+ consent_body . contains( "Deny" ) ,
101165 "The consent page should contain a Deny button"
102166 ) ;
103167
104- // Step 2: Simulate user consent (accept)
168+ // Step 4: Extract consent form action and simulate user consent (accept)
105169 println ! ( "Simulating user consent (Accept)..." ) ;
106- let consent_response = client
107- . post ( format ! ( "/authorize?{}&allow=true" , query_params) )
108- . dispatch ( )
109- . await ;
170+
171+ // Extract the form action for the Accept button
172+ let accept_form_regex = regex:: Regex :: new ( r#"<form method="post" action="([^"]*allow=true[^"]*)">"# ) . unwrap ( ) ;
173+ let consent_action = accept_form_regex
174+ . captures ( & consent_body)
175+ . and_then ( |caps| caps. get ( 1 ) )
176+ . map ( |m| m. as_str ( ) . to_string ( ) )
177+ . expect ( "Should extract form action for consent acceptance" ) ;
178+
179+ println ! ( "Consent form action: {}" , consent_action) ;
180+
181+ // Submit the consent form
182+ let consent_submit_response = client. post ( & consent_action) . dispatch ( ) . await ;
110183
111184 // The response should be a redirect to the callback URI with an authorization code
112- assert_eq ! ( consent_response . status( ) , Status :: Found ) ;
185+ assert_eq ! ( consent_submit_response . status( ) , Status :: Found ) ;
113186
114187 // Extract the redirect URL and authorization code
115- let location_header = consent_response
188+ let location_header = consent_submit_response
116189 . headers ( )
117190 . get_one ( "Location" )
118191 . expect ( "Location header missing in response" ) ;
119- println ! ( "Redirect URL: {}" , location_header) ;
192+ println ! ( "Final redirect URL: {}" , location_header) ;
120193
121194 let params = extract_params_from_url ( location_header) ;
122195 let authorization_code = params. get ( "code" ) . expect ( "Authorization code missing" ) ;
123196 println ! ( "Authorization code: {}" , authorization_code) ;
124197
125- // Step 3 : Exchange authorization code for tokens
198+ // Step 5 : Exchange authorization code for tokens
126199 println ! ( "Exchanging authorization code for tokens..." ) ;
127200 let token_body = format ! (
128201 "grant_type=authorization_code&code={}&redirect_uri=http://localhost:8080/client/&client_id=LaserSmartClient&code_verifier={}&code_challenge_method=S256" ,
@@ -152,6 +225,7 @@ async fn test_oauth2_pkce_flow() {
152225 token_json. get( "access_token" ) . is_some( ) ,
153226 "Response should contain an access_token"
154227 ) ;
228+
155229 // Note: The token_type case is not standardized, but our implementation uses lowercase
156230 assert_eq ! (
157231 token_json
@@ -187,6 +261,39 @@ async fn test_oauth2_pkce_flow() {
187261 // We could decode the payload too, but this is enough to verify it's a JWT
188262 }
189263
264+ // Step 6: Test with invalid credentials (should fail)
265+ println ! ( "Testing with invalid credentials..." ) ;
266+
267+ // Make a new authorization request
268+ let invalid_auth_response = client
269+ . get ( format ! ( "/authorize?{}" , query_params) )
270+ . dispatch ( )
271+ . await ;
272+
273+ assert_eq ! ( invalid_auth_response. status( ) , Status :: Ok ) ;
274+
275+ // Try to login with invalid credentials
276+ let mut invalid_form_data = HashMap :: new ( ) ;
277+ invalid_form_data. insert ( "username" , "admin" ) ;
278+ invalid_form_data. insert ( "password" , "wrongpassword" ) ;
279+ invalid_form_data. insert ( "response_type" , "code" ) ;
280+ invalid_form_data. insert ( "client_id" , "LaserSmartClient" ) ;
281+ invalid_form_data. insert ( "redirect_uri" , "http://localhost:8080/client/" ) ;
282+
283+ let invalid_login_response = client
284+ . post ( "/login" )
285+ . header ( ContentType :: Form )
286+ . body ( serde_urlencoded:: to_string ( & invalid_form_data) . unwrap ( ) )
287+ . dispatch ( )
288+ . await ;
289+
290+ // Should return unauthorized status or redirect back to login
291+ assert ! (
292+ invalid_login_response. status( ) == Status :: Unauthorized ||
293+ invalid_login_response. status( ) == Status :: Ok , // Might return OK with error message
294+ "Invalid credentials should be rejected"
295+ ) ;
296+
190297 // The complete OAuth2 PKCE flow has been successfully tested
191- println ! ( "OAuth2 PKCE flow test completed successfully!" ) ;
192- }
298+ println ! ( "OAuth2 PKCE flow test with authentication completed successfully!" ) ;
299+ }
0 commit comments