chore: redirect url (#134)

* chore: redirect url

* chore: stop ws conn if error is auth error

* chore: add query params

* chore: fix clippy
This commit is contained in:
Nathan.fooo 2023-10-24 00:40:44 +08:00 committed by GitHub
parent 7c503372e0
commit 49f994488a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 12 deletions

View File

@ -59,6 +59,9 @@ pub struct Client {
token: Arc<RwLock<ClientToken>>,
}
/// Hardcoded schema in the frontend application. Do not change this value.
const DESKTOP_CALLBACK_URL: &str = "appflowy-flutter://login-callback";
impl Client {
/// Constructs a new `Client` instance.
///
@ -165,7 +168,9 @@ impl Client {
///
/// For example, the OAuth URL on Google looks like `https://appflowy.io/authorize?provider=google`.
/// The deep link looks like `appflowy-flutter://#access_token=...&expires_in=3600&provider_token=...&refresh_token=...&token_type=bearer`.
///
/// The appflowy-flutter:// is a hardcoded schema in the frontend application
///
/// # Parameters
/// - `provider`: A reference to an `OAuthProvider` indicating which OAuth provider to use for login.
///
@ -183,11 +188,30 @@ impl Client {
return Err(ErrorCode::InvalidOAuthProvider.into());
}
Ok(format!(
"{}/authorize?provider={}&redirect_to=appflowy-flutter://",
self.gotrue_client.base_url,
provider.as_str(),
))
let url = format!("{}/authorize", self.gotrue_client.base_url,);
let mut url = Url::parse(&url)?;
url
.query_pairs_mut()
.append_pair("provider", provider.as_str())
.append_pair("redirect_to", DESKTOP_CALLBACK_URL);
if let OAuthProvider::Google = provider {
url
.query_pairs_mut()
// In many cases, especially for server-side applications or mobile apps that might need to
// interact with Google services on behalf of the user without the user being actively
// engaged, access_type=offline is preferred to ensure long-term access.
.append_pair("access_type", "offline")
// In Google OAuth2.0, the prompt parameter is used to control the OAuth2.0 flow's behavior.
// It determines if the user is re-prompted for authentication and/or consent.
// 1. none: The authorization server does not display any authentication or consent user interface pages.
// 2. consent: The authorization server prompts the user for consent before returning information to the client
// 3. select_account: The authorization server prompts the user to select a user account.
.append_pair("prompt", "consent");
}
Ok(url.to_string())
}
/// Returns an OAuth URL by constructing the authorization URL for the specified provider.
@ -500,7 +524,7 @@ impl Client {
.token
.read()
.as_ref()
.ok_or::<AppError>(ErrorCode::NotLoggedIn.into())?
.ok_or(AppError::new(ErrorCode::NotLoggedIn, "No access token"))?
.refresh_token
.as_str()
.to_owned();

View File

@ -244,7 +244,12 @@ struct RetryCondition {
addr: Weak<parking_lot::Mutex<Option<String>>>,
}
impl Condition<WSError> for RetryCondition {
fn should_retry(&mut self, _error: &WSError) -> bool {
fn should_retry(&mut self, error: &WSError) -> bool {
if let WSError::AuthError(err) = error {
debug!("WSClient auth error: {}, stop retry connn", err);
return false;
}
let should_retry = self
.addr
.upgrade()

View File

@ -1,13 +1,18 @@
use crate::ws::ClientRealtimeMessage;
use reqwest::StatusCode;
use tokio_tungstenite::tungstenite::Error;
#[derive(Debug, thiserror::Error)]
pub enum WSError {
#[error(transparent)]
Tungstenite(#[from] tokio_tungstenite::tungstenite::error::Error),
#[error("Unsupported ws message type")]
UnsupportedMsgType,
#[error(transparent)]
TungsteniteError(Error),
#[error("Auth error: {0}")]
AuthError(String),
#[error(transparent)]
SerdeError(#[from] serde_json::Error),
@ -17,3 +22,14 @@ pub enum WSError {
#[error("Internal failure: {0}")]
Internal(#[from] Box<dyn std::error::Error + Send + Sync>),
}
impl From<Error> for WSError {
fn from(value: Error) -> Self {
if let Error::Http(resp) = &value {
if resp.status() == StatusCode::UNAUTHORIZED {
return WSError::AuthError("Unauthorized ws connection".to_string());
}
}
WSError::TungsteniteError(value)
}
}

View File

@ -44,7 +44,6 @@ impl actix_web::error::ResponseError for AppError {
actix_web::HttpResponse::Ok().json(self)
}
}
//
impl From<anyhow::Error> for AppError {
fn from(err: anyhow::Error) -> Self {
match err.downcast::<AppError>() {

View File

@ -14,6 +14,7 @@ use crate::component::auth::jwt::{authorization_from_token, UserUuid};
use database::user::select_uid_from_uuid;
use shared_entity::app_error::AppError;
use std::time::Duration;
use tracing::instrument;
pub fn ws_scope() -> Scope {
web::scope("/ws").service(establish_ws_connection)
@ -24,6 +25,8 @@ const MAX_FRAME_SIZE: usize = 65_536; // 64 KiB
type CollabServerData = Data<
Addr<CollabServer<CollabPostgresDBStorage, Arc<RealtimeUserImpl>, Arc<CollabAccessControlImpl>>>,
>;
#[instrument(skip_all, err)]
#[get("/{token}/{device_id}")]
pub async fn establish_ws_connection(
request: HttpRequest,

View File

@ -8,6 +8,7 @@ use sqlx::types::{uuid, Uuid};
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use std::str::FromStr;
use tracing::instrument;
use crate::state::AppState;
@ -126,6 +127,7 @@ fn get_auth_from_request(req: &HttpRequest) -> Result<Authorization, actix_web::
authorization_from_token(token, state)
}
#[instrument(skip_all, err)]
pub fn authorization_from_token(
token: &str,
state: &Data<AppState>,
@ -137,6 +139,7 @@ pub fn authorization_from_token(
})
}
#[instrument(skip_all, err)]
fn gotrue_jwt_claims_from_token(
token: &str,
state: &Data<AppState>,