use actix_http::StatusCode; use actix_web::test; use common::{ api_v3::oauth::get_redirect_location_query_params, api_v3::oauth::{get_auth_code_from_redirect_params, get_authorize_accept_flow_id}, asserts::{assert_any_status_except, assert_status}, database::FRIEND_USER_ID, database::{FRIEND_USER_PAT, USER_USER_ID, USER_USER_PAT}, dummy_data::DummyOAuthClientAlpha, environment::with_test_environment, }; use labrinth::auth::oauth::TokenResponse; use reqwest::header::{CACHE_CONTROL, PRAGMA}; mod common; #[actix_rt::test] async fn oauth_flow_happy_path() { with_test_environment(|env| async move { let DummyOAuthClientAlpha { valid_redirect_uri: base_redirect_uri, client_id, client_secret, } = env.dummy.as_ref().unwrap().oauth_client_alpha.clone(); // Initiate authorization let redirect_uri = format!("{}?foo=bar", base_redirect_uri); let original_state = "1234"; let resp = env .v3 .oauth_authorize( &client_id, Some("USER_READ NOTIFICATION_READ"), Some(&redirect_uri), Some(original_state), FRIEND_USER_PAT, ) .await; assert_status(&resp, StatusCode::OK); let flow_id = get_authorize_accept_flow_id(resp).await; // Accept the authorization request let resp = env.v3.oauth_accept(&flow_id, FRIEND_USER_PAT).await; assert_status(&resp, StatusCode::OK); let query = get_redirect_location_query_params(&resp); let auth_code = query.get("code").unwrap(); let state = query.get("state").unwrap(); let foo_val = query.get("foo").unwrap(); assert_eq!(state, original_state); assert_eq!(foo_val, "bar"); // Get the token let resp = env .v3 .oauth_token( auth_code.to_string(), Some(redirect_uri.clone()), client_id.to_string(), &client_secret, ) .await; assert_status(&resp, StatusCode::OK); assert_eq!(resp.headers().get(CACHE_CONTROL).unwrap(), "no-store"); assert_eq!(resp.headers().get(PRAGMA).unwrap(), "no-cache"); let token_resp: TokenResponse = test::read_body_json(resp).await; // Validate the token works env.assert_read_notifications_status( FRIEND_USER_ID, &token_resp.access_token, StatusCode::OK, ) .await; }) .await; } #[actix_rt::test] async fn oauth_authorize_for_already_authorized_scopes_returns_auth_code() { with_test_environment(|env| async { let DummyOAuthClientAlpha { client_id, .. } = env.dummy.unwrap().oauth_client_alpha.clone(); let resp = env .v3 .oauth_authorize( &client_id, Some("USER_READ NOTIFICATION_READ"), None, Some("1234"), USER_USER_PAT, ) .await; let flow_id = get_authorize_accept_flow_id(resp).await; env.v3.oauth_accept(&flow_id, USER_USER_PAT).await; let resp = env .v3 .oauth_authorize( &client_id, Some("USER_READ"), None, Some("5678"), USER_USER_PAT, ) .await; assert_status(&resp, StatusCode::OK); }) .await; } #[actix_rt::test] async fn get_oauth_token_with_already_used_auth_code_fails() { with_test_environment(|env| async { let DummyOAuthClientAlpha { client_id, client_secret, .. } = env.dummy.unwrap().oauth_client_alpha.clone(); let resp = env .v3 .oauth_authorize(&client_id, None, None, None, USER_USER_PAT) .await; let flow_id = get_authorize_accept_flow_id(resp).await; let resp = env.v3.oauth_accept(&flow_id, USER_USER_PAT).await; let auth_code = get_auth_code_from_redirect_params(&resp).await; let resp = env .v3 .oauth_token(auth_code.clone(), None, client_id.clone(), &client_secret) .await; assert_status(&resp, StatusCode::OK); let resp = env .v3 .oauth_token(auth_code, None, client_id, &client_secret) .await; assert_status(&resp, StatusCode::BAD_REQUEST); }) .await; } #[actix_rt::test] async fn authorize_with_broader_scopes_can_complete_flow() { with_test_environment(|env| async move { let DummyOAuthClientAlpha { client_id, client_secret, .. } = env.dummy.as_ref().unwrap().oauth_client_alpha.clone(); let first_access_token = env .v3 .complete_full_authorize_flow( &client_id, &client_secret, Some("PROJECT_READ"), None, None, USER_USER_PAT, ) .await; let second_access_token = env .v3 .complete_full_authorize_flow( &client_id, &client_secret, Some("PROJECT_READ NOTIFICATION_READ"), None, None, USER_USER_PAT, ) .await; env.assert_read_notifications_status( USER_USER_ID, &first_access_token, StatusCode::UNAUTHORIZED, ) .await; env.assert_read_user_projects_status(USER_USER_ID, &first_access_token, StatusCode::OK) .await; env.assert_read_notifications_status(USER_USER_ID, &second_access_token, StatusCode::OK) .await; env.assert_read_user_projects_status(USER_USER_ID, &second_access_token, StatusCode::OK) .await; }) .await; } #[actix_rt::test] async fn oauth_authorize_with_broader_scopes_requires_user_accept() { with_test_environment(|env| async { let client_id = env.dummy.unwrap().oauth_client_alpha.client_id.clone(); let resp = env .v3 .oauth_authorize(&client_id, Some("USER_READ"), None, None, USER_USER_PAT) .await; let flow_id = get_authorize_accept_flow_id(resp).await; env.v3.oauth_accept(&flow_id, USER_USER_PAT).await; let resp = env .v3 .oauth_authorize( &client_id, Some("USER_READ NOTIFICATION_READ"), None, None, USER_USER_PAT, ) .await; assert_status(&resp, StatusCode::OK); get_authorize_accept_flow_id(resp).await; // ensure we can deser this without error to really confirm }) .await; } #[actix_rt::test] async fn reject_authorize_ends_authorize_flow() { with_test_environment(|env| async move { let client_id = env.dummy.unwrap().oauth_client_alpha.client_id.clone(); let resp = env .v3 .oauth_authorize(&client_id, None, None, None, USER_USER_PAT) .await; let flow_id = get_authorize_accept_flow_id(resp).await; let resp = env.v3.oauth_reject(&flow_id, USER_USER_PAT).await; assert_status(&resp, StatusCode::OK); let resp = env.v3.oauth_accept(&flow_id, USER_USER_PAT).await; assert_any_status_except(&resp, StatusCode::OK); }) .await; } #[actix_rt::test] async fn accept_authorize_after_already_accepting_fails() { with_test_environment(|env| async move { let client_id = env.dummy.unwrap().oauth_client_alpha.client_id.clone(); let resp = env .v3 .oauth_authorize(&client_id, None, None, None, USER_USER_PAT) .await; let flow_id = get_authorize_accept_flow_id(resp).await; let resp = env.v3.oauth_accept(&flow_id, USER_USER_PAT).await; assert_status(&resp, StatusCode::OK); let resp = env.v3.oauth_accept(&flow_id, USER_USER_PAT).await; assert_status(&resp, StatusCode::BAD_REQUEST); }) .await; } #[actix_rt::test] async fn revoke_authorization_after_issuing_token_revokes_token() { with_test_environment(|env| async move { let DummyOAuthClientAlpha { client_id, client_secret, .. } = env.dummy.as_ref().unwrap().oauth_client_alpha.clone(); let access_token = env .v3 .complete_full_authorize_flow( &client_id, &client_secret, Some("NOTIFICATION_READ"), None, None, USER_USER_PAT, ) .await; env.assert_read_notifications_status(USER_USER_ID, &access_token, StatusCode::OK) .await; let resp = env .v3 .revoke_oauth_authorization(&client_id, USER_USER_PAT) .await; assert_status(&resp, StatusCode::OK); env.assert_read_notifications_status(USER_USER_ID, &access_token, StatusCode::UNAUTHORIZED) .await; }) .await; }