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",
|
"opener",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"scraper",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
|
|
@ -4038,9 +4039,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-tungstenite"
|
name = "tokio-tungstenite"
|
||||||
version = "0.20.0"
|
version = "0.20.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2"
|
checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -4182,9 +4183,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
version = "0.20.0"
|
version = "0.20.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649"
|
checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,11 @@ once_cell = "1.7.2"
|
||||||
collab-plugins = { version = "0.1.0", features = ["sync_plugin"] }
|
collab-plugins = { version = "0.1.0", features = ["sync_plugin"] }
|
||||||
collab-define = { version = "0.1.0" }
|
collab-define = { version = "0.1.0" }
|
||||||
collab-sync-protocol = { version = "0.1.0" }
|
collab-sync-protocol = { version = "0.1.0" }
|
||||||
client-api = { path = "libs/client-api" }
|
|
||||||
tempfile = "3.4.0"
|
tempfile = "3.4.0"
|
||||||
assert-json-diff = "2.0.2"
|
assert-json-diff = "2.0.2"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
scraper = "0.17.1"
|
scraper = "0.17.1"
|
||||||
|
client-api = { path = "libs/client-api", features = ["client-api-test"]}
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "appflowy_cloud"
|
name = "appflowy_cloud"
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,17 @@ parking_lot = "0.12.1"
|
||||||
tracing = { version = "0.1" }
|
tracing = { version = "0.1" }
|
||||||
thiserror = "1.0.39"
|
thiserror = "1.0.39"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio-tungstenite = { version = "0.20" }
|
tokio-tungstenite = { version = "0.20.1" }
|
||||||
tokio = { version = "1.26", features = ["full"] }
|
tokio = { version = "1.26", features = ["full"] }
|
||||||
futures-util = "0.3.26"
|
futures-util = "0.3.26"
|
||||||
tokio-retry = "0.3"
|
tokio-retry = "0.3"
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
uuid = "1.4.1"
|
uuid = "1.4.1"
|
||||||
|
scraper = { version = "0.17.1", optional = true }
|
||||||
collab-sync-protocol = { version = "0.1.0" }
|
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};
|
use storage_entity::{DeleteCollabParams, RawData};
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
cloud_client: reqwest::Client,
|
pub(crate) cloud_client: reqwest::Client,
|
||||||
gotrue_client: gotrue::api::Client,
|
pub(crate) gotrue_client: gotrue::api::Client,
|
||||||
base_url: String,
|
base_url: String,
|
||||||
ws_addr: String,
|
ws_addr: String,
|
||||||
token: Arc<RwLock<ClientToken>>,
|
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;
|
mod http;
|
||||||
|
|
||||||
|
#[cfg(feature = "client-api-test")]
|
||||||
|
pub mod http_test;
|
||||||
|
|
||||||
pub mod notify;
|
pub mod notify;
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@ async fn retrieve_snapshot_data_handler(
|
||||||
Ok(Json(AppResponse::Ok().with_data(data)))
|
Ok(Json(AppResponse::Ok().with_data(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn retrieve_snapshots_handler(
|
async fn retrieve_snapshots_handler(
|
||||||
payload: Json<QueryObjectSnapshotParams>,
|
payload: Json<QueryObjectSnapshotParams>,
|
||||||
storage: Data<Storage<CollabStorageProxy>>,
|
storage: Data<Storage<CollabStorageProxy>>,
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ async fn verify_handler(
|
||||||
Ok(AppResponse::Ok().with_data(resp).into())
|
Ok(AppResponse::Ok().with_data(resp).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(state))]
|
||||||
async fn profile_handler(
|
async fn profile_handler(
|
||||||
uuid: UserUuid,
|
uuid: UserUuid,
|
||||||
state: Data<AppState>,
|
state: Data<AppState>,
|
||||||
|
|
@ -59,6 +60,7 @@ async fn update_handler(
|
||||||
Ok(AppResponse::Ok().into())
|
Ok(AppResponse::Ok().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn login_handler(
|
async fn login_handler(
|
||||||
req: Json<LoginRequest>,
|
req: Json<LoginRequest>,
|
||||||
state: Data<AppState>,
|
state: Data<AppState>,
|
||||||
|
|
@ -83,6 +85,7 @@ async fn login_handler(
|
||||||
Ok(HttpResponse::Ok().json(resp))
|
Ok(HttpResponse::Ok().json(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip(state))]
|
||||||
async fn logout_handler(req: HttpRequest, state: Data<AppState>) -> Result<HttpResponse> {
|
async fn logout_handler(req: HttpRequest, state: Data<AppState>) -> Result<HttpResponse> {
|
||||||
let logged_user = logged_user_from_request(&req, &state.config.application.server_key)?;
|
let logged_user = logged_user_from_request(&req, &state.config.application.server_key)?;
|
||||||
logout(logged_user, state.user.clone()).await;
|
logout(logged_user, state.user.clone()).await;
|
||||||
|
|
@ -109,6 +112,7 @@ async fn register_handler(
|
||||||
Ok(HttpResponse::Ok().json(resp))
|
Ok(HttpResponse::Ok().json(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn change_password_handler(
|
async fn change_password_handler(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
payload: Json<ChangePasswordRequest>,
|
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 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;
|
use crate::client_api_client;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -87,7 +87,7 @@ async fn sign_in_success() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[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 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();
|
let c = client_api_client();
|
||||||
match c.sign_in_url(url_str).await {
|
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::{
|
use gotrue::{
|
||||||
api::Client,
|
api::Client,
|
||||||
grant::{Grant, PasswordGrant},
|
grant::{Grant, PasswordGrant},
|
||||||
params::{AdminUserParams, GenerateLinkParams},
|
params::{AdminUserParams, GenerateLinkParams},
|
||||||
};
|
};
|
||||||
use scraper::{Html, Selector};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{
|
client::{
|
||||||
|
|
@ -95,16 +95,3 @@ async fn admin_generate_link_and_user_sign_in() {
|
||||||
let workspaces = client.workspaces().await.unwrap();
|
let workspaces = client.workspaces().await.unwrap();
|
||||||
assert_eq!(workspaces.len(), 1);
|
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