feat: added refresh ability on server and client (#41)
* feat: added refresh ability on server and client * fix: use refresh token for refresh and add test case * chore: cargo fmt --all * chore: cargo clippy * fix: cargo clippy * test: added async mutex for registered user for consistency * fix: remove unneeded files --------- Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
parent
e03a6ce587
commit
b3be09e264
|
|
@ -1,4 +1,3 @@
|
||||||
use anyhow::Error;
|
|
||||||
use gotrue_entity::OAuthProvider;
|
use gotrue_entity::OAuthProvider;
|
||||||
use gotrue_entity::OAuthURL;
|
use gotrue_entity::OAuthURL;
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
|
|
@ -146,6 +145,19 @@ impl Client {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn refresh(&mut self) -> Result<(), AppError> {
|
||||||
|
let refresh_token = self
|
||||||
|
.token
|
||||||
|
.as_ref()
|
||||||
|
.ok_or::<AppError>(ErrorCode::NotLoggedIn.into())?
|
||||||
|
.refresh_token
|
||||||
|
.as_str();
|
||||||
|
let url = format!("{}/api/user/refresh/{}", self.base_url, refresh_token);
|
||||||
|
let resp = self.http_client.get(&url).send().await?;
|
||||||
|
self.token = AppResponse::from_response(resp).await?.into_data()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn sign_up(&self, email: &str, password: &str) -> Result<(), AppError> {
|
pub async fn sign_up(&self, email: &str, password: &str) -> Result<(), AppError> {
|
||||||
let url = format!("{}/api/user/sign_up", self.base_url);
|
let url = format!("{}/api/user/sign_up", self.base_url);
|
||||||
let payload = serde_json::json!({
|
let payload = serde_json::json!({
|
||||||
|
|
@ -239,15 +251,16 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn http_client_with_auth(&self, method: Method, url: &str) -> Result<RequestBuilder, Error> {
|
fn http_client_with_auth(&self, method: Method, url: &str) -> Result<RequestBuilder, AppError> {
|
||||||
match &self.token {
|
match &self.token {
|
||||||
None => anyhow::bail!("no token found, are you logged in?"),
|
None => Err(ErrorCode::NotLoggedIn.into()),
|
||||||
Some(t) => Ok(
|
Some(t) => {
|
||||||
self
|
let request_builder = self
|
||||||
.http_client
|
.http_client
|
||||||
.request(method, url)
|
.request(method, url)
|
||||||
.bearer_auth(t.access_token.to_string()),
|
.bearer_auth(&t.access_token);
|
||||||
),
|
Ok(request_builder)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
pub enum Grant {
|
pub enum Grant {
|
||||||
Password(PasswordGrant),
|
Password(PasswordGrant),
|
||||||
RefreshToken,
|
RefreshToken(RefreshTokenGrant),
|
||||||
IdToken,
|
IdToken,
|
||||||
PKCE,
|
PKCE,
|
||||||
}
|
}
|
||||||
|
|
@ -10,11 +10,15 @@ pub struct PasswordGrant {
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RefreshTokenGrant {
|
||||||
|
pub refresh_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Grant {
|
impl Grant {
|
||||||
pub fn type_as_str(&self) -> &str {
|
pub fn type_as_str(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Grant::Password(_) => "password",
|
Grant::Password(_) => "password",
|
||||||
Grant::RefreshToken => "refresh_token",
|
Grant::RefreshToken(_) => "refresh_token",
|
||||||
Grant::IdToken => "id_token",
|
Grant::IdToken => "id_token",
|
||||||
Grant::PKCE => "password",
|
Grant::PKCE => "password",
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +32,11 @@ impl Grant {
|
||||||
"password": p.password,
|
"password": p.password,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
Grant::RefreshToken => todo!(),
|
Grant::RefreshToken(r) => {
|
||||||
|
serde_json::json!({
|
||||||
|
"refresh_token": r.refresh_token,
|
||||||
|
})
|
||||||
|
},
|
||||||
Grant::IdToken => todo!(),
|
Grant::IdToken => todo!(),
|
||||||
Grant::PKCE => todo!(),
|
Grant::PKCE => todo!(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,9 @@ pub enum ErrorCode {
|
||||||
|
|
||||||
#[error("Invalid OAuth Provider")]
|
#[error("Invalid OAuth Provider")]
|
||||||
InvalidOAuthProvider = 1010,
|
InvalidOAuthProvider = 1010,
|
||||||
|
|
||||||
|
#[error("Not Logged In")]
|
||||||
|
NotLoggedIn = 1011,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements conversion from `anyhow::Error` to `ErrorCode`.
|
/// Implements conversion from `anyhow::Error` to `ErrorCode`.
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ pub fn user_scope() -> Scope {
|
||||||
.service(web::resource("/update").route(web::post().to(update_handler)))
|
.service(web::resource("/update").route(web::post().to(update_handler)))
|
||||||
.service(web::resource("/oauth/{provider}").route(web::get().to(oauth_handler)))
|
.service(web::resource("/oauth/{provider}").route(web::get().to(oauth_handler)))
|
||||||
.service(web::resource("/info/{access_token}").route(web::get().to(info_handler)))
|
.service(web::resource("/info/{access_token}").route(web::get().to(info_handler)))
|
||||||
|
.service(web::resource("/refresh/{refresh_token}").route(web::get().to(refresh_handler)))
|
||||||
|
|
||||||
.service(web::resource("/workspaces").route(web::get().to(workspaces_handler)))
|
.service(web::resource("/workspaces").route(web::get().to(workspaces_handler)))
|
||||||
.service(web::resource("/profile").route(web::get().to(profile_handler)))
|
.service(web::resource("/profile").route(web::get().to(profile_handler)))
|
||||||
|
|
@ -40,6 +41,15 @@ pub fn user_scope() -> Scope {
|
||||||
.service(web::resource("/password").route(web::post().to(change_password_handler)))
|
.service(web::resource("/password").route(web::post().to(change_password_handler)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn refresh_handler(
|
||||||
|
path: web::Path<String>,
|
||||||
|
state: Data<State>,
|
||||||
|
) -> Result<JsonAppResponse<AccessTokenResponse>> {
|
||||||
|
let refresh_token = path.into_inner();
|
||||||
|
let oauth_url = biz::user::refresh(&state.gotrue_client, refresh_token).await?;
|
||||||
|
Ok(AppResponse::Ok().with_data(oauth_url).into())
|
||||||
|
}
|
||||||
|
|
||||||
async fn info_handler(
|
async fn info_handler(
|
||||||
path: web::Path<String>,
|
path: web::Path<String>,
|
||||||
state: Data<State>,
|
state: Data<State>,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::str::FromStr;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gotrue::{
|
use gotrue::{
|
||||||
api::Client,
|
api::Client,
|
||||||
grant::{Grant, PasswordGrant},
|
grant::{Grant, PasswordGrant, RefreshTokenGrant},
|
||||||
};
|
};
|
||||||
use gotrue_entity::{AccessTokenResponse, OAuthProvider, OAuthURL, User};
|
use gotrue_entity::{AccessTokenResponse, OAuthProvider, OAuthURL, User};
|
||||||
use shared_entity::{
|
use shared_entity::{
|
||||||
|
|
@ -17,6 +17,15 @@ use crate::domain::validate_password;
|
||||||
use sqlx::{types::uuid, PgPool};
|
use sqlx::{types::uuid, PgPool};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
pub async fn refresh(
|
||||||
|
gotrue_client: &Client,
|
||||||
|
refresh_token: String,
|
||||||
|
) -> Result<AccessTokenResponse, AppError> {
|
||||||
|
let grant = Grant::RefreshToken(RefreshTokenGrant { refresh_token });
|
||||||
|
let token = gotrue_client.token(&grant).await??;
|
||||||
|
Ok(token)
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(level = "info", skip_all, err)]
|
#[instrument(level = "info", skip_all, err)]
|
||||||
pub async fn sign_up(
|
pub async fn sign_up(
|
||||||
gotrue_client: &Client,
|
gotrue_client: &Client,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
|
mod refresh;
|
||||||
mod sign_in;
|
mod sign_in;
|
||||||
mod sign_out;
|
mod sign_out;
|
||||||
mod sign_up;
|
mod sign_up;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
use crate::{
|
||||||
|
client::utils::{REGISTERED_EMAIL, REGISTERED_PASSWORD, REGISTERED_USER_MUTEX},
|
||||||
|
client_api_client,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn refresh_success() {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
|
let email = ®ISTERED_EMAIL;
|
||||||
|
let password = ®ISTERED_PASSWORD;
|
||||||
|
let mut c = client_api_client();
|
||||||
|
c.sign_in_password(email, password).await.unwrap();
|
||||||
|
c.refresh().await.unwrap();
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
use shared_entity::error_code::ErrorCode;
|
use shared_entity::error_code::ErrorCode;
|
||||||
|
|
||||||
use crate::client::utils::{generate_unique_email, REGISTERED_EMAIL, REGISTERED_PASSWORD};
|
use crate::client::utils::{
|
||||||
|
generate_unique_email, REGISTERED_EMAIL, REGISTERED_PASSWORD, REGISTERED_USER_MUTEX,
|
||||||
|
};
|
||||||
use crate::client_api_client;
|
use crate::client_api_client;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -47,6 +49,8 @@ async fn sign_in_unconfirmed_email() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn sign_in_success() {
|
async fn sign_in_success() {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
let mut c = client_api_client();
|
let mut c = client_api_client();
|
||||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::client::utils::{REGISTERED_EMAIL, REGISTERED_PASSWORD};
|
use crate::client::utils::{REGISTERED_EMAIL, REGISTERED_PASSWORD, REGISTERED_USER_MUTEX};
|
||||||
use crate::client_api_client;
|
use crate::client_api_client;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -10,8 +10,9 @@ async fn sign_out_but_not_sign_in() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn sign_out_after_sign_in() {
|
async fn sign_out_after_sign_in() {
|
||||||
let mut c = client_api_client();
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
|
let mut c = client_api_client();
|
||||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ use gotrue_entity::OAuthProvider;
|
||||||
use shared_entity::error_code::ErrorCode;
|
use shared_entity::error_code::ErrorCode;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
client::utils::{generate_unique_email, REGISTERED_EMAIL, REGISTERED_PASSWORD},
|
client::utils::{
|
||||||
|
generate_unique_email, REGISTERED_EMAIL, REGISTERED_PASSWORD, REGISTERED_USER_MUTEX,
|
||||||
|
},
|
||||||
client_api_client,
|
client_api_client,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -36,6 +38,8 @@ async fn sign_up_invalid_password() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn sign_up_but_existing_user() {
|
async fn sign_up_but_existing_user() {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
let c = client_api_client();
|
let c = client_api_client();
|
||||||
c.sign_up(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
c.sign_up(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::client::utils::{generate_unique_email, REGISTERED_EMAIL, REGISTERED_PASSWORD};
|
use crate::client::utils::{
|
||||||
|
generate_unique_email, REGISTERED_EMAIL, REGISTERED_PASSWORD, REGISTERED_USER_MUTEX,
|
||||||
|
};
|
||||||
use crate::client_api_client;
|
use crate::client_api_client;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -12,6 +14,8 @@ async fn update_but_not_logged_in() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn update_password_same_password() {
|
async fn update_password_same_password() {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
let mut c = client_api_client();
|
let mut c = client_api_client();
|
||||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
|
@ -23,6 +27,8 @@ async fn update_password_same_password() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn update_password_and_revert() {
|
async fn update_password_and_revert() {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
let new_password = "Hello456!";
|
let new_password = "Hello456!";
|
||||||
{
|
{
|
||||||
// change password to new_password
|
// change password to new_password
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
|
@ -12,6 +13,7 @@ lazy_static! {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
std::env::var("GOTRUE_REGISTERED_PASSWORD").unwrap()
|
std::env::var("GOTRUE_REGISTERED_PASSWORD").unwrap()
|
||||||
};
|
};
|
||||||
|
pub static ref REGISTERED_USER_MUTEX: Mutex<()> = Mutex::new(());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timestamp_nano() -> u128 {
|
pub fn timestamp_nano() -> u128 {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::client::utils::{REGISTERED_EMAIL, REGISTERED_PASSWORD};
|
use crate::client::utils::{REGISTERED_EMAIL, REGISTERED_PASSWORD, REGISTERED_USER_MUTEX};
|
||||||
use crate::client_api_client;
|
use crate::client_api_client;
|
||||||
use crate::collab::workspace_id_from_client;
|
use crate::collab::workspace_id_from_client;
|
||||||
|
|
||||||
|
|
@ -9,6 +9,8 @@ use uuid::Uuid;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn success_insert_collab_test() {
|
async fn success_insert_collab_test() {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
let mut c = client_api_client();
|
let mut c = client_api_client();
|
||||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
|
@ -40,6 +42,8 @@ async fn success_insert_collab_test() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn success_delete_collab_test() {
|
async fn success_delete_collab_test() {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
let mut c = client_api_client();
|
let mut c = client_api_client();
|
||||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
|
@ -77,6 +81,8 @@ async fn success_delete_collab_test() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn fail_insert_collab_with_empty_payload_test() {
|
async fn fail_insert_collab_with_empty_payload_test() {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
let mut c = client_api_client();
|
let mut c = client_api_client();
|
||||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
|
@ -99,6 +105,8 @@ async fn fail_insert_collab_with_empty_payload_test() {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn fail_insert_collab_with_invalid_workspace_id_test() {
|
async fn fail_insert_collab_with_invalid_workspace_id_test() {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
let mut c = client_api_client();
|
let mut c = client_api_client();
|
||||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::client::utils::{REGISTERED_EMAIL, REGISTERED_PASSWORD};
|
use crate::client::utils::{REGISTERED_EMAIL, REGISTERED_PASSWORD, REGISTERED_USER_MUTEX};
|
||||||
use crate::client_api_client;
|
use crate::client_api_client;
|
||||||
|
|
||||||
use collab_ws::{ConnectState, WSClient, WSClientConfig};
|
use collab_ws::{ConnectState, WSClient, WSClientConfig};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn realtime_connect_test() {
|
async fn realtime_connect_test() {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
let mut c = client_api_client();
|
let mut c = client_api_client();
|
||||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::client::utils::{REGISTERED_EMAIL, REGISTERED_PASSWORD};
|
use crate::client::utils::{REGISTERED_EMAIL, REGISTERED_PASSWORD, REGISTERED_USER_MUTEX};
|
||||||
|
|
||||||
use client_api::Client;
|
use client_api::Client;
|
||||||
use collab::core::collab::MutexCollab;
|
use collab::core::collab::MutexCollab;
|
||||||
|
|
@ -26,6 +26,8 @@ pub(crate) struct TestClient {
|
||||||
|
|
||||||
impl TestClient {
|
impl TestClient {
|
||||||
pub(crate) async fn new(client: &mut Client, object_id: &str, collab_type: CollabType) -> Self {
|
pub(crate) async fn new(client: &mut Client, object_id: &str, collab_type: CollabType) -> Self {
|
||||||
|
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||||
|
|
||||||
// Sign in
|
// Sign in
|
||||||
client
|
client
|
||||||
.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue