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::OAuthURL;
|
||||
use reqwest::Method;
|
||||
|
|
@ -146,6 +145,19 @@ impl Client {
|
|||
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> {
|
||||
let url = format!("{}/api/user/sign_up", self.base_url);
|
||||
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 {
|
||||
None => anyhow::bail!("no token found, are you logged in?"),
|
||||
Some(t) => Ok(
|
||||
self
|
||||
None => Err(ErrorCode::NotLoggedIn.into()),
|
||||
Some(t) => {
|
||||
let request_builder = self
|
||||
.http_client
|
||||
.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 {
|
||||
Password(PasswordGrant),
|
||||
RefreshToken,
|
||||
RefreshToken(RefreshTokenGrant),
|
||||
IdToken,
|
||||
PKCE,
|
||||
}
|
||||
|
|
@ -10,11 +10,15 @@ pub struct PasswordGrant {
|
|||
pub password: String,
|
||||
}
|
||||
|
||||
pub struct RefreshTokenGrant {
|
||||
pub refresh_token: String,
|
||||
}
|
||||
|
||||
impl Grant {
|
||||
pub fn type_as_str(&self) -> &str {
|
||||
match self {
|
||||
Grant::Password(_) => "password",
|
||||
Grant::RefreshToken => "refresh_token",
|
||||
Grant::RefreshToken(_) => "refresh_token",
|
||||
Grant::IdToken => "id_token",
|
||||
Grant::PKCE => "password",
|
||||
}
|
||||
|
|
@ -28,7 +32,11 @@ impl Grant {
|
|||
"password": p.password,
|
||||
})
|
||||
},
|
||||
Grant::RefreshToken => todo!(),
|
||||
Grant::RefreshToken(r) => {
|
||||
serde_json::json!({
|
||||
"refresh_token": r.refresh_token,
|
||||
})
|
||||
},
|
||||
Grant::IdToken => todo!(),
|
||||
Grant::PKCE => todo!(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ pub enum ErrorCode {
|
|||
|
||||
#[error("Invalid OAuth Provider")]
|
||||
InvalidOAuthProvider = 1010,
|
||||
|
||||
#[error("Not Logged In")]
|
||||
NotLoggedIn = 1011,
|
||||
}
|
||||
|
||||
/// 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("/oauth/{provider}").route(web::get().to(oauth_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("/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)))
|
||||
}
|
||||
|
||||
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(
|
||||
path: web::Path<String>,
|
||||
state: Data<State>,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::str::FromStr;
|
|||
use anyhow::Result;
|
||||
use gotrue::{
|
||||
api::Client,
|
||||
grant::{Grant, PasswordGrant},
|
||||
grant::{Grant, PasswordGrant, RefreshTokenGrant},
|
||||
};
|
||||
use gotrue_entity::{AccessTokenResponse, OAuthProvider, OAuthURL, User};
|
||||
use shared_entity::{
|
||||
|
|
@ -17,6 +17,15 @@ use crate::domain::validate_password;
|
|||
use sqlx::{types::uuid, PgPool};
|
||||
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)]
|
||||
pub async fn sign_up(
|
||||
gotrue_client: &Client,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod constants;
|
||||
mod refresh;
|
||||
mod sign_in;
|
||||
mod sign_out;
|
||||
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 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;
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -47,6 +49,8 @@ async fn sign_in_unconfirmed_email() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn sign_in_success() {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
let mut c = client_api_client();
|
||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||
.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;
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -10,8 +10,9 @@ async fn sign_out_but_not_sign_in() {
|
|||
|
||||
#[tokio::test]
|
||||
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)
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ use gotrue_entity::OAuthProvider;
|
|||
use shared_entity::error_code::ErrorCode;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
|
@ -36,6 +38,8 @@ async fn sign_up_invalid_password() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn sign_up_but_existing_user() {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
let c = client_api_client();
|
||||
c.sign_up(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||
.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;
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -12,6 +14,8 @@ async fn update_but_not_logged_in() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn update_password_same_password() {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
let mut c = client_api_client();
|
||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||
.await
|
||||
|
|
@ -23,6 +27,8 @@ async fn update_password_same_password() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn update_password_and_revert() {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
let new_password = "Hello456!";
|
||||
{
|
||||
// change password to new_password
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use dotenv::dotenv;
|
||||
use std::time::SystemTime;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
|
|
@ -12,6 +13,7 @@ lazy_static! {
|
|||
dotenv().ok();
|
||||
std::env::var("GOTRUE_REGISTERED_PASSWORD").unwrap()
|
||||
};
|
||||
pub static ref REGISTERED_USER_MUTEX: Mutex<()> = Mutex::new(());
|
||||
}
|
||||
|
||||
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::collab::workspace_id_from_client;
|
||||
|
||||
|
|
@ -9,6 +9,8 @@ use uuid::Uuid;
|
|||
|
||||
#[tokio::test]
|
||||
async fn success_insert_collab_test() {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
let mut c = client_api_client();
|
||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||
.await
|
||||
|
|
@ -40,6 +42,8 @@ async fn success_insert_collab_test() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn success_delete_collab_test() {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
let mut c = client_api_client();
|
||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||
.await
|
||||
|
|
@ -77,6 +81,8 @@ async fn success_delete_collab_test() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn fail_insert_collab_with_empty_payload_test() {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
let mut c = client_api_client();
|
||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||
.await
|
||||
|
|
@ -99,6 +105,8 @@ async fn fail_insert_collab_with_empty_payload_test() {
|
|||
|
||||
#[tokio::test]
|
||||
async fn fail_insert_collab_with_invalid_workspace_id_test() {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
let mut c = client_api_client();
|
||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||
.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 collab_ws::{ConnectState, WSClient, WSClientConfig};
|
||||
|
||||
#[tokio::test]
|
||||
async fn realtime_connect_test() {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
let mut c = client_api_client();
|
||||
c.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||
.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 collab::core::collab::MutexCollab;
|
||||
|
|
@ -26,6 +26,8 @@ pub(crate) struct TestClient {
|
|||
|
||||
impl TestClient {
|
||||
pub(crate) async fn new(client: &mut Client, object_id: &str, collab_type: CollabType) -> Self {
|
||||
let _guard = REGISTERED_USER_MUTEX.lock().await;
|
||||
|
||||
// Sign in
|
||||
client
|
||||
.sign_in_password(®ISTERED_EMAIL, ®ISTERED_PASSWORD)
|
||||
|
|
|
|||
Loading…
Reference in New Issue