From b3be09e264299f278ad5fe0635ddc1baef5950ec Mon Sep 17 00:00:00 2001 From: Zack <33050391+speed2exe@users.noreply.github.com> Date: Thu, 14 Sep 2023 15:58:18 +0800 Subject: [PATCH] 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 --- libs/client-api/src/http.rs | 27 ++++++++++++++++++++------- libs/gotrue/src/grant.rs | 14 +++++++++++--- libs/shared-entity/src/error_code.rs | 3 +++ src/api/user.rs | 10 ++++++++++ src/biz/user.rs | 11 ++++++++++- tests/client/mod.rs | 1 + tests/client/refresh.rs | 15 +++++++++++++++ tests/client/sign_in.rs | 6 +++++- tests/client/sign_out.rs | 5 +++-- tests/client/sign_up.rs | 6 +++++- tests/client/update.rs | 8 +++++++- tests/client/utils.rs | 2 ++ tests/collab/storage_test.rs | 10 +++++++++- tests/realtime/connect_test.rs | 4 +++- tests/realtime/test_client.rs | 4 +++- 15 files changed, 107 insertions(+), 19 deletions(-) create mode 100644 tests/client/refresh.rs diff --git a/libs/client-api/src/http.rs b/libs/client-api/src/http.rs index ec08db1c..2a2c1d76 100644 --- a/libs/client-api/src/http.rs +++ b/libs/client-api/src/http.rs @@ -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::(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 { + fn http_client_with_auth(&self, method: Method, url: &str) -> Result { 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) + }, } } diff --git a/libs/gotrue/src/grant.rs b/libs/gotrue/src/grant.rs index a697da66..965193f8 100644 --- a/libs/gotrue/src/grant.rs +++ b/libs/gotrue/src/grant.rs @@ -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!(), } diff --git a/libs/shared-entity/src/error_code.rs b/libs/shared-entity/src/error_code.rs index 57a265c7..f569a081 100644 --- a/libs/shared-entity/src/error_code.rs +++ b/libs/shared-entity/src/error_code.rs @@ -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`. diff --git a/src/api/user.rs b/src/api/user.rs index 94f061fe..88299ca9 100644 --- a/src/api/user.rs +++ b/src/api/user.rs @@ -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, + state: Data, +) -> Result> { + 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, state: Data, diff --git a/src/biz/user.rs b/src/biz/user.rs index 066e5cc3..41b43474 100644 --- a/src/biz/user.rs +++ b/src/biz/user.rs @@ -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 { + 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, diff --git a/tests/client/mod.rs b/tests/client/mod.rs index a19192d4..070cb7ef 100644 --- a/tests/client/mod.rs +++ b/tests/client/mod.rs @@ -1,4 +1,5 @@ pub mod constants; +mod refresh; mod sign_in; mod sign_out; mod sign_up; diff --git a/tests/client/refresh.rs b/tests/client/refresh.rs new file mode 100644 index 00000000..4e9db6bb --- /dev/null +++ b/tests/client/refresh.rs @@ -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(); +} diff --git a/tests/client/sign_in.rs b/tests/client/sign_in.rs index c2d253c3..ac4797a6 100644 --- a/tests/client/sign_in.rs +++ b/tests/client/sign_in.rs @@ -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 diff --git a/tests/client/sign_out.rs b/tests/client/sign_out.rs index 6a537403..8b8709a3 100644 --- a/tests/client/sign_out.rs +++ b/tests/client/sign_out.rs @@ -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(); diff --git a/tests/client/sign_up.rs b/tests/client/sign_up.rs index 7374fc4e..703f5386 100644 --- a/tests/client/sign_up.rs +++ b/tests/client/sign_up.rs @@ -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 diff --git a/tests/client/update.rs b/tests/client/update.rs index 1ae42cf9..2cd67fa2 100644 --- a/tests/client/update.rs +++ b/tests/client/update.rs @@ -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 diff --git a/tests/client/utils.rs b/tests/client/utils.rs index d9b27d1f..9659bc89 100644 --- a/tests/client/utils.rs +++ b/tests/client/utils.rs @@ -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 { diff --git a/tests/collab/storage_test.rs b/tests/collab/storage_test.rs index b3480af8..1f43d773 100644 --- a/tests/collab/storage_test.rs +++ b/tests/collab/storage_test.rs @@ -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 diff --git a/tests/realtime/connect_test.rs b/tests/realtime/connect_test.rs index a4bc8bff..bd837510 100644 --- a/tests/realtime/connect_test.rs +++ b/tests/realtime/connect_test.rs @@ -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 diff --git a/tests/realtime/test_client.rs b/tests/realtime/test_client.rs index 6b47d734..95227fed 100644 --- a/tests/realtime/test_client.rs +++ b/tests/realtime/test_client.rs @@ -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)