diff --git a/.sqlx/query-3b3dd104bf9e1fb9cf6d413e0e59db0cdbb009c92dce1c1e67900c44efdb8db4.json b/.sqlx/query-5d2ab1eb7ab9ebea8c162f4b742c72dbea7f3d18849f6cd7865068be9b01d759.json similarity index 66% rename from .sqlx/query-3b3dd104bf9e1fb9cf6d413e0e59db0cdbb009c92dce1c1e67900c44efdb8db4.json rename to .sqlx/query-5d2ab1eb7ab9ebea8c162f4b742c72dbea7f3d18849f6cd7865068be9b01d759.json index 340d60a7..9be3d004 100644 --- a/.sqlx/query-3b3dd104bf9e1fb9cf6d413e0e59db0cdbb009c92dce1c1e67900c44efdb8db4.json +++ b/.sqlx/query-5d2ab1eb7ab9ebea8c162f4b742c72dbea7f3d18849f6cd7865068be9b01d759.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE public.af_workspace_invitation\n SET status = 1\n WHERE invitee_email = (SELECT email FROM public.af_user WHERE uuid = $1)\n AND id = $2\n ", + "query": "\n UPDATE public.af_workspace_invitation\n SET status = 1\n WHERE invitee_email = (SELECT email FROM public.af_user WHERE uuid = $1)\n AND id = $2\n AND status = 0\n ", "describe": { "columns": [], "parameters": { @@ -11,5 +11,5 @@ }, "nullable": [] }, - "hash": "3b3dd104bf9e1fb9cf6d413e0e59db0cdbb009c92dce1c1e67900c44efdb8db4" + "hash": "5d2ab1eb7ab9ebea8c162f4b742c72dbea7f3d18849f6cd7865068be9b01d759" } diff --git a/admin_frontend/src/ext/api.rs b/admin_frontend/src/ext/api.rs index c4c1d883..12d34a67 100644 --- a/admin_frontend/src/ext/api.rs +++ b/admin_frontend/src/ext/api.rs @@ -143,6 +143,23 @@ pub async fn get_pending_workspace_invitations( from_json_response(resp).await } +pub async fn get_accepted_workspace_invitations( + access_token: &str, + appflowy_cloud_base_url: &str, +) -> Result, Error> { + let http_client = reqwest::Client::new(); + let resp = http_client + .get(format!( + "{}/api/workspace/invite?status=Accepted", + appflowy_cloud_base_url + )) + .header("Authorization", format!("Bearer {}", access_token)) + .send() + .await?; + + from_json_response(resp).await +} + async fn get_user_workspace_limits( workspace_id: &str, access_token: &str, diff --git a/admin_frontend/src/web_app.rs b/admin_frontend/src/web_app.rs index c1135a86..05665a1c 100644 --- a/admin_frontend/src/web_app.rs +++ b/admin_frontend/src/web_app.rs @@ -1,9 +1,10 @@ use crate::askama_entities::WorkspaceWithMembers; use crate::error::WebAppError; use crate::ext::api::{ - accept_workspace_invitation, get_pending_workspace_invitations, get_user_owned_workspaces, - get_user_profile, get_user_workspace_limit, get_user_workspace_usages, get_user_workspaces, - get_workspace_members, verify_token_cloud, + accept_workspace_invitation, get_accepted_workspace_invitations, + get_pending_workspace_invitations, get_user_owned_workspaces, get_user_profile, + get_user_workspace_limit, get_user_workspace_usages, get_user_workspaces, get_workspace_members, + verify_token_cloud, }; use crate::models::{OAuthLoginAction, WebAppOAuthLoginRequest}; use crate::session::{self, new_session_cookie, UserSession}; @@ -68,35 +69,51 @@ async fn login_callback_handler() -> Result, WebAppError> { async fn login_callback_query_handler( State(state): State, + session: Option, Query(query): Query, mut jar: CookieJar, ) -> Result<(CookieJar, Html), WebAppError> { - if let Some(err) = query.error { - tracing::error!( - "OAuth login error: {:?}, code: {:?}, description: {:?}", - err, - query.error_code, - query.error_description - ); - return Ok((jar, render_template(templates::Redirect { - redirect_url: format!( - "https://appflowy.io/invitation/expired?workspace_name={}&workspace_icon={}&user_name={}&user_icon={}&workspace_member_count={}", - query.workspace_name.unwrap_or_default(), - query.workspace_icon.unwrap_or_default(), - query.user_name.unwrap_or_default(), - query.user_icon.unwrap_or_default(), - query.workspace_member_count.unwrap_or_default()), - })?)); + let refresh_token = { + match query.refresh_token { + Some(refresh_token) => refresh_token, + None => match session { + Some(session) => session.token.refresh_token, + None => match query.error { + Some(err) => { + tracing::error!( + "OAuth login error: {:?}, code: {:?}, description: {:?}", + err, + query.error_code, + query.error_description + ); + let expired_url = format!( + "https://appflowy.io/invitation/expired?workspace_name={}&workspace_icon={}&user_name={}&user_icon={}&workspace_member_count={}", + query.workspace_name.unwrap_or_default(), + query.workspace_icon.unwrap_or_default(), + query.user_name.unwrap_or_default(), + query.user_icon.unwrap_or_default(), + query.workspace_member_count.unwrap_or_default()); + return Ok(( + jar, + render_template(templates::Redirect { + redirect_url: expired_url, + })?, + )); + }, + None => { + return Err(WebAppError::BadRequest( + "refresh_token not found".to_string(), + )); + }, + }, + }, + } }; let token = state .gotrue_client .token(&gotrue::grant::Grant::RefreshToken( - gotrue::grant::RefreshTokenGrant { - refresh_token: query.refresh_token.ok_or(WebAppError::BadRequest( - "refresh_token not found".to_string(), - ))?, - }, + gotrue::grant::RefreshTokenGrant { refresh_token }, )) .await?; @@ -129,6 +146,22 @@ async fn login_callback_query_handler( .ok_or(WebAppError::BadRequest( "workspace_invitation_id not found".to_string(), ))?; + + { + // If user has already accepted the invitation, redirect to open or download AppFlowy + let accepted_invitations = get_accepted_workspace_invitations( + &new_session.token.access_token, + &state.appflowy_cloud_url, + ) + .await?; + let found = accepted_invitations + .iter() + .find(|w| w.invite_id.to_string() == invite_id); + if let Some(_) = found { + return Ok((jar, render_template(templates::OpenAppFlowyOrDownload {})?)); + } + } + if let Err(err) = accept_workspace_invitation( &new_session.token.access_token, &invite_id, @@ -137,10 +170,17 @@ async fn login_callback_query_handler( .await { tracing::error!("accepting workspace invitation: {:?}", err); + let expired_url = format!( + "https://appflowy.io/invitation/expired?workspace_name={}&workspace_icon={}&user_name={}&user_icon={}&workspace_member_count={}", + query.workspace_name.unwrap_or_default(), + query.workspace_icon.unwrap_or_default(), + query.user_name.unwrap_or_default(), + query.user_icon.unwrap_or_default(), + query.workspace_member_count.unwrap_or_default()); return Ok(( jar, render_template(templates::Redirect { - redirect_url: "https://test.appflowy.io/invitation/expired".to_string(), + redirect_url: expired_url, })?, )); }; diff --git a/libs/database/src/workspace.rs b/libs/database/src/workspace.rs index 188c2149..5e57c9c8 100644 --- a/libs/database/src/workspace.rs +++ b/libs/database/src/workspace.rs @@ -294,6 +294,7 @@ pub async fn update_workspace_invitation_set_status_accepted( SET status = 1 WHERE invitee_email = (SELECT email FROM public.af_user WHERE uuid = $1) AND id = $2 + AND status = 0 "#, invitee_uuid, invite_id,