test: sign in url test (#75)
* test: sign in url test * fix: Tungstenite allows remote attackers to cause a denial of service * chore: enable feature * chore: update
This commit is contained in:
parent
e3ff765137
commit
0883ae94b8
|
|
@ -839,6 +839,7 @@ dependencies = [
|
|||
"opener",
|
||||
"parking_lot",
|
||||
"reqwest",
|
||||
"scraper",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
|
|
@ -4038,9 +4039,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.20.0"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2"
|
||||
checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
|
|
@ -4182,9 +4183,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
|||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.20.0"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649"
|
||||
checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
|
|
|
|||
|
|
@ -80,11 +80,11 @@ once_cell = "1.7.2"
|
|||
collab-plugins = { version = "0.1.0", features = ["sync_plugin"] }
|
||||
collab-define = { version = "0.1.0" }
|
||||
collab-sync-protocol = { version = "0.1.0" }
|
||||
client-api = { path = "libs/client-api" }
|
||||
tempfile = "3.4.0"
|
||||
assert-json-diff = "2.0.2"
|
||||
dotenv = "0.15.0"
|
||||
scraper = "0.17.1"
|
||||
client-api = { path = "libs/client-api", features = ["client-api-test"]}
|
||||
|
||||
[[bin]]
|
||||
name = "appflowy_cloud"
|
||||
|
|
|
|||
|
|
@ -22,11 +22,17 @@ parking_lot = "0.12.1"
|
|||
tracing = { version = "0.1" }
|
||||
thiserror = "1.0.39"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio-tungstenite = { version = "0.20" }
|
||||
tokio-tungstenite = { version = "0.20.1" }
|
||||
tokio = { version = "1.26", features = ["full"] }
|
||||
futures-util = "0.3.26"
|
||||
tokio-retry = "0.3"
|
||||
bytes = "1.0"
|
||||
uuid = "1.4.1"
|
||||
|
||||
scraper = { version = "0.17.1", optional = true }
|
||||
collab-sync-protocol = { version = "0.1.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
scraper = "0.17.1"
|
||||
|
||||
[features]
|
||||
client-api-test = ["scraper"]
|
||||
|
|
@ -27,8 +27,8 @@ use storage_entity::{AFWorkspaces, QueryCollabParams};
|
|||
use storage_entity::{DeleteCollabParams, RawData};
|
||||
|
||||
pub struct Client {
|
||||
cloud_client: reqwest::Client,
|
||||
gotrue_client: gotrue::api::Client,
|
||||
pub(crate) cloud_client: reqwest::Client,
|
||||
pub(crate) gotrue_client: gotrue::api::Client,
|
||||
base_url: String,
|
||||
ws_addr: String,
|
||||
token: Arc<RwLock<ClientToken>>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
use crate::Client;
|
||||
use gotrue::grant::{Grant, PasswordGrant};
|
||||
use gotrue::params::GenerateLinkParams;
|
||||
use gotrue_entity::GoTrueError;
|
||||
use scraper::{Html, Selector};
|
||||
|
||||
impl Client {
|
||||
pub async fn generate_sign_in_url(
|
||||
&self,
|
||||
admin_user_email: &str,
|
||||
admin_user_password: &str,
|
||||
user_email: &str,
|
||||
) -> Result<String, GoTrueError> {
|
||||
let admin_token = self
|
||||
.gotrue_client
|
||||
.token(&Grant::Password(PasswordGrant {
|
||||
email: admin_user_email.to_string(),
|
||||
password: admin_user_password.to_string(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
let admin_user_params: GenerateLinkParams = GenerateLinkParams {
|
||||
email: user_email.to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let link_resp = self
|
||||
.gotrue_client
|
||||
.generate_link(&admin_token.access_token, &admin_user_params)
|
||||
.await?;
|
||||
assert_eq!(link_resp.email, user_email);
|
||||
|
||||
let action_link = link_resp.action_link;
|
||||
let resp = reqwest::Client::new().get(action_link).send().await?;
|
||||
let resp_text = resp.text().await?;
|
||||
Ok(extract_appflowy_sign_in_url(&resp_text))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_appflowy_sign_in_url(html_str: &str) -> String {
|
||||
let fragment = Html::parse_fragment(html_str);
|
||||
let selector = Selector::parse("a").unwrap();
|
||||
fragment
|
||||
.select(&selector)
|
||||
.next()
|
||||
.unwrap()
|
||||
.value()
|
||||
.attr("href")
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
mod http;
|
||||
|
||||
#[cfg(feature = "client-api-test")]
|
||||
pub mod http_test;
|
||||
|
||||
pub mod notify;
|
||||
pub mod ws;
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ async fn retrieve_snapshot_data_handler(
|
|||
Ok(Json(AppResponse::Ok().with_data(data)))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn retrieve_snapshots_handler(
|
||||
payload: Json<QueryObjectSnapshotParams>,
|
||||
storage: Data<Storage<CollabStorageProxy>>,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ async fn verify_handler(
|
|||
Ok(AppResponse::Ok().with_data(resp).into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(state))]
|
||||
async fn profile_handler(
|
||||
uuid: UserUuid,
|
||||
state: Data<AppState>,
|
||||
|
|
@ -59,6 +60,7 @@ async fn update_handler(
|
|||
Ok(AppResponse::Ok().into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn login_handler(
|
||||
req: Json<LoginRequest>,
|
||||
state: Data<AppState>,
|
||||
|
|
@ -83,6 +85,7 @@ async fn login_handler(
|
|||
Ok(HttpResponse::Ok().json(resp))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(state))]
|
||||
async fn logout_handler(req: HttpRequest, state: Data<AppState>) -> Result<HttpResponse> {
|
||||
let logged_user = logged_user_from_request(&req, &state.config.application.server_key)?;
|
||||
logout(logged_user, state.user.clone()).await;
|
||||
|
|
@ -109,6 +112,7 @@ async fn register_handler(
|
|||
Ok(HttpResponse::Ok().json(resp))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn change_password_handler(
|
||||
req: HttpRequest,
|
||||
payload: Json<ChangePasswordRequest>,
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
use crate::client::utils::{register_deep_fake, LOCALHOST_URL};
|
||||
use appflowy_cloud::client::http;
|
||||
|
||||
#[tokio::test]
|
||||
async fn login_success() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let (email, _user, password) = register_deep_fake(&mut c).await;
|
||||
let initial_token = c.logged_in_token().unwrap().to_string();
|
||||
c.login(&email, &password).await.unwrap();
|
||||
let relogin_token = c.logged_in_token().unwrap();
|
||||
assert_ne!(&initial_token, relogin_token);
|
||||
assert!(c.logged_in_token().is_some())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn login_with_empty_email() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let (_email, _user, password) = register_deep_fake(&mut c).await;
|
||||
assert!(c.login("", &password).await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn login_with_empty_password() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let (email, _user, _password) = register_deep_fake(&mut c).await;
|
||||
assert!(c.login(&email, "").await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn login_with_unknown_user() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let token = c.login("unknown@appflowy.io", "Abc@123!").await;
|
||||
assert!(token.is_err());
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
use crate::client::utils::{register_deep_fake, LOCALHOST_URL};
|
||||
use appflowy_cloud::client::http;
|
||||
|
||||
#[tokio::test]
|
||||
async fn change_password_with_unmatch_password() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let new_password = "HelloWord@1a";
|
||||
let new_password_confirm = "HeloWord@1a";
|
||||
let (_email, _user, password) = register_deep_fake(&mut c).await;
|
||||
let res = c
|
||||
.change_password(&password, new_password, new_password_confirm)
|
||||
.await;
|
||||
assert!(res.is_err())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn login_failed_after_change_password() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let new_password = "HelloWord@1a";
|
||||
let (email, _user, old_password) = register_deep_fake(&mut c).await;
|
||||
let res = c
|
||||
.change_password(&old_password, new_password, new_password)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
let res = c.login(&email, &old_password).await;
|
||||
assert!(res.is_err())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn login_success_after_change_password() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let new_password = "HelloWord@1a";
|
||||
let (email, _user, old_password) = register_deep_fake(&mut c).await;
|
||||
let res = c
|
||||
.change_password(&old_password, new_password, new_password)
|
||||
.await;
|
||||
assert!(res.is_ok());
|
||||
let res = c.login(&email, new_password).await;
|
||||
assert!(res.is_ok())
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
use crate::client::utils::timestamp_nano;
|
||||
use crate::client::utils::LOCALHOST_URL;
|
||||
use appflowy_cloud::client::http;
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_success() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let email = format!("deep_fake{}@appflowy.io", timestamp_nano());
|
||||
c.register("user1", &email, "DeepFakePassword!123")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(c.logged_in_token().is_some())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_with_invalid_password() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let email = format!("deep_fake{}@appflowy.io", timestamp_nano());
|
||||
let res = c.register("user1", &email, "123").await;
|
||||
assert!(res.is_err());
|
||||
assert!(c.logged_in_token().is_none())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_with_invalid_name() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let email = format!("deep_fake{}@appflowy.io", timestamp_nano());
|
||||
let res = c.register("", &email, "DeepFakePassword!123").await;
|
||||
assert!(res.is_err());
|
||||
assert!(c.logged_in_token().is_none())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_with_invalid_email() {
|
||||
let c = http::Client::from(reqwest::Client::new(), LOCALHOST_URL);
|
||||
let res = c
|
||||
.register("user1", "appflowy.io", "DeepFakePassword!123")
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
assert!(c.logged_in_token().is_none())
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use shared_entity::error_code::ErrorCode;
|
||||
|
||||
use crate::client::utils::{generate_unique_email, generate_unique_registered_user};
|
||||
use crate::client::utils::{generate_unique_email, generate_unique_registered_user, ADMIN_USER};
|
||||
use crate::client_api_client;
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -87,7 +87,7 @@ async fn sign_in_success() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sign_in_with_url() {
|
||||
async fn sign_in_with_invalid_url() {
|
||||
let url_str = "appflowy-flutter://#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTQ1ODIyMjMsInN1YiI6Ijk5MGM2NDNjLTMyMWEtNGNmMi04OWY1LTNhNmJhZGFjMTg5NCIsImVtYWlsIjoiNG5uaWhpbGF0ZWRAZ21haWwuY29tIiwicGhvbmUiOiIiLCJhcHBfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJnb29nbGUiLCJwcm92aWRlcnMiOlsiZ29vZ2xlIl19LCJ1c2VyX21ldGFkYXRhIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NJdGZpa28xX0lpMmZiNzM4VnpGekViLVBqT0NCY3FUQzdrNjVIX0hnRTQwOVk9czk2LWMiLCJlbWFpbCI6IjRubmloaWxhdGVkQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmdWxsX25hbWUiOiJmdSB6aXhpYW5nIiwiaXNzIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vdXNlcmluZm8vdjIvbWUiLCJuYW1lIjoiZnUgeml4aWFuZyIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS9BQ2c4b2NJdGZpa28xX0lpMmZiNzM4VnpGekViLVBqT0NCY3FUQzdrNjVIX0hnRTQwOVk9czk2LWMiLCJwcm92aWRlcl9pZCI6IjEwMTQ5OTYxMDMxOTYxNjE0NTcyNSIsInN1YiI6IjEwMTQ5OTYxMDMxOTYxNjE0NTcyNSJ9LCJyb2xlIjoiIn0.I-7j-Tdj62P56zhzEqvBc7cHMldv5MA_MM7xtrBibbE&expires_in=3600&provider_token=ya29.a0AfB_byCovXs1CUiC9_f9VBTupQPsIxwh9aSlOg0PLYJvv1x1zvVfssrQfW6_Aq9no7EKpCzFUCLElOvK1Xz4x4K5r7tug79tr5b1yiOoUMWTeWTXyV61fZHQbZ9vscAiyKYtq5NqYTiytHcQEFlKr7UMfu6BTbKsUwaCgYKAaISARISFQGOcNnC0Vsx2QCAXgYO3XbfcF91WQ0169&refresh_token=Hi3Jc3I_pj9YrexcR91i5g&token_type=bearer";
|
||||
let c = client_api_client();
|
||||
match c.sign_in_url(url_str).await {
|
||||
|
|
@ -100,3 +100,14 @@ async fn sign_in_with_url() {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sign_in_with_url() {
|
||||
let c = client_api_client();
|
||||
let user_email = generate_unique_email();
|
||||
let url_str = c
|
||||
.generate_sign_in_url(&ADMIN_USER.email, &ADMIN_USER.password, &user_email)
|
||||
.await
|
||||
.unwrap();
|
||||
let _ = c.sign_in_url(&url_str).await.unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use client_api::http_test::extract_appflowy_sign_in_url;
|
||||
use gotrue::{
|
||||
api::Client,
|
||||
grant::{Grant, PasswordGrant},
|
||||
params::{AdminUserParams, GenerateLinkParams},
|
||||
};
|
||||
use scraper::{Html, Selector};
|
||||
|
||||
use crate::{
|
||||
client::{
|
||||
|
|
@ -95,16 +95,3 @@ async fn admin_generate_link_and_user_sign_in() {
|
|||
let workspaces = client.workspaces().await.unwrap();
|
||||
assert_eq!(workspaces.len(), 1);
|
||||
}
|
||||
|
||||
fn extract_appflowy_sign_in_url(html_str: &str) -> String {
|
||||
let fragment = Html::parse_fragment(html_str);
|
||||
let selector = Selector::parse("a").unwrap();
|
||||
fragment
|
||||
.select(&selector)
|
||||
.next()
|
||||
.unwrap()
|
||||
.value()
|
||||
.attr("href")
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue