chore: revoke for apple user
This commit is contained in:
parent
7c36c712c8
commit
25cec9982f
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM auth.users WHERE id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e6acbe78f0e8f776901c560088222939e80ec2e75747503f0493d081ba43e4bd"
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::notify::{ClientToken, TokenStateReceiver};
|
||||
use app_error::AppError;
|
||||
use client_api_entity::auth_dto::DeleteUserQuery;
|
||||
use client_api_entity::workspace_dto::QueryWorkspaceParam;
|
||||
use client_api_entity::AuthProvider;
|
||||
use client_api_entity::CollabType;
|
||||
|
|
@ -738,15 +739,33 @@ impl Client {
|
|||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
||||
/// Deletes the user account and all associated data.
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
pub async fn delete_user(&self) -> Result<(), AppResponseError> {
|
||||
let (provider_access_token, provider_refresh_token) = {
|
||||
let token = self.token();
|
||||
let token_read = token.read();
|
||||
let token_resp = token_read
|
||||
.as_ref()
|
||||
.ok_or(AppResponseError::from(AppError::NotLoggedIn(
|
||||
"token is empty".to_string(),
|
||||
)))?;
|
||||
(
|
||||
token_resp.provider_access_token.clone(),
|
||||
token_resp.provider_refresh_token.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
let url = format!("{}/api/user", self.base_url);
|
||||
let resp = self
|
||||
.http_client_with_auth(Method::DELETE, &url)
|
||||
.await?
|
||||
.query(&DeleteUserQuery {
|
||||
provider_access_token,
|
||||
provider_refresh_token,
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
log_request_id(&resp);
|
||||
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,24 +240,3 @@ pub async fn select_name_from_uuid(pool: &PgPool, user_uuid: &Uuid) -> Result<St
|
|||
.await?;
|
||||
Ok(email)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn delete_user(pool: &PgPool, user_uuid: &Uuid) -> Result<(), AppError> {
|
||||
let res = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM auth.users WHERE id = $1
|
||||
"#,
|
||||
user_uuid
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
if res.rows_affected() != 1 {
|
||||
return Err(AppError::RecordNotFound(format!(
|
||||
"User with UUID {} not found",
|
||||
user_uuid
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use infra::reqwest::{check_response, from_body, from_response};
|
|||
use reqwest::{Method, RequestBuilder};
|
||||
use tracing::event;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Client {
|
||||
client: reqwest::Client,
|
||||
pub base_url: String,
|
||||
|
|
|
|||
|
|
@ -65,3 +65,9 @@ pub struct SignInPasswordResponse {
|
|||
pub struct SignInTokenResponse {
|
||||
pub is_new: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct DeleteUserQuery {
|
||||
pub provider_access_token: Option<String>,
|
||||
pub provider_refresh_token: Option<String>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::biz::user::user_info::{delete_user, get_profile, get_user_workspace_info, update_user};
|
||||
use crate::biz::user::user_info::{get_profile, get_user_workspace_info, update_user};
|
||||
use crate::biz::user::user_verify::verify_token;
|
||||
use crate::state::AppState;
|
||||
use actix_web::web::{Data, Json};
|
||||
|
|
@ -7,8 +7,9 @@ use actix_web::{web, Scope};
|
|||
use app_error::ErrorCode;
|
||||
use authentication::jwt::{Authorization, UserUuid};
|
||||
use database_entity::dto::{AFUserProfile, AFUserWorkspaceInfo};
|
||||
use secrecy::ExposeSecret;
|
||||
use shared_entity::dto::auth_dto::{SignInTokenResponse, UpdateUserParams};
|
||||
use gotrue::params::AdminDeleteUserParams;
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
use shared_entity::dto::auth_dto::{DeleteUserQuery, SignInTokenResponse, UpdateUserParams};
|
||||
use shared_entity::response::AppResponseError;
|
||||
use shared_entity::response::{AppResponse, JsonAppResponse};
|
||||
|
||||
|
|
@ -69,24 +70,69 @@ async fn update_user_handler(
|
|||
async fn delete_user_handler(
|
||||
auth: Authorization,
|
||||
state: Data<AppState>,
|
||||
) -> Result<JsonAppResponse<()>> {
|
||||
delete_user(&state.pg_pool, auth.uuid()?).await?;
|
||||
|
||||
if is_apple_user(auth) {
|
||||
if let Err(err) = revoke_apple_token(
|
||||
query: web::Query<DeleteUserQuery>,
|
||||
) -> Result<JsonAppResponse<()>, actix_web::Error> {
|
||||
let user_uuid = auth.uuid()?;
|
||||
if is_apple_user(&auth) {
|
||||
let query = query.into_inner();
|
||||
revoke_apple_user(
|
||||
&state.config.apple_oauth.client_id,
|
||||
state.config.apple_oauth.client_secret.expose_secret(),
|
||||
"TODO: get original apple during oauth",
|
||||
&state.config.apple_oauth.client_secret,
|
||||
query.provider_access_token,
|
||||
query.provider_refresh_token,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let admin_token = state.gotrue_admin.token().await?;
|
||||
let _ = &state
|
||||
.gotrue_client
|
||||
.admin_delete_user(
|
||||
&admin_token,
|
||||
&user_uuid.to_string(),
|
||||
&AdminDeleteUserParams {
|
||||
should_soft_delete: false,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!("revoke apple token failed: {:?}", err);
|
||||
};
|
||||
}
|
||||
.map_err(AppResponseError::from)?;
|
||||
|
||||
Ok(AppResponse::Ok().into())
|
||||
}
|
||||
|
||||
fn is_apple_user(auth: Authorization) -> bool {
|
||||
async fn revoke_apple_user(
|
||||
client_id: &str,
|
||||
client_secret: &Secret<String>,
|
||||
apple_access_token: Option<String>,
|
||||
apple_refresh_token: Option<String>,
|
||||
) -> Result<(), AppResponseError> {
|
||||
let (type_type_hint, token) = match apple_access_token {
|
||||
Some(access_token) => ("access_token", access_token),
|
||||
None => match apple_refresh_token {
|
||||
Some(refresh_token) => ("refresh_token", refresh_token),
|
||||
None => {
|
||||
return Err(AppResponseError::new(
|
||||
ErrorCode::InvalidRequest,
|
||||
"apple email deletion must provide access_token or refresh_token",
|
||||
))
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if let Err(err) = revoke_apple_token_http_call(
|
||||
client_id,
|
||||
client_secret.expose_secret(),
|
||||
&token,
|
||||
type_type_hint,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!("revoke apple token failed: {:?}", err);
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_apple_user(auth: &Authorization) -> bool {
|
||||
if let Some(provider) = auth.claims.app_metadata.get("provider") {
|
||||
if provider == "apple" {
|
||||
return true;
|
||||
|
|
@ -107,10 +153,11 @@ fn is_apple_user(auth: Authorization) -> bool {
|
|||
}
|
||||
|
||||
/// Based on: https://developer.apple.com/documentation/sign_in_with_apple/revoke_tokens
|
||||
async fn revoke_apple_token(
|
||||
async fn revoke_apple_token_http_call(
|
||||
apple_client_id: &str,
|
||||
apple_client_secret: &str,
|
||||
apple_user_token: &str,
|
||||
token_type_hint: &str,
|
||||
) -> Result<(), AppResponseError> {
|
||||
let resp = reqwest::Client::new()
|
||||
.post("https://appleid.apple.com/auth/revoke")
|
||||
|
|
@ -118,6 +165,7 @@ async fn revoke_apple_token(
|
|||
("client_id", apple_client_id),
|
||||
("client_secret", apple_client_secret),
|
||||
("token", apple_user_token),
|
||||
("token_type_hint", token_type_hint),
|
||||
])
|
||||
.send()
|
||||
.await?;
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
|
|||
// Gotrue
|
||||
info!("Connecting to GoTrue...");
|
||||
let gotrue_client = get_gotrue_client(&config.gotrue).await?;
|
||||
let gotrue_admin = setup_admin_account(&gotrue_client, &pg_pool, &config.gotrue).await?;
|
||||
let gotrue_admin = setup_admin_account(gotrue_client.clone(), &pg_pool, &config.gotrue).await?;
|
||||
|
||||
// Redis
|
||||
info!("Connecting to Redis...");
|
||||
|
|
@ -320,13 +320,17 @@ pub async fn init_state(config: &Config, rt_cmd_tx: CLCommandSender) -> Result<A
|
|||
}
|
||||
|
||||
async fn setup_admin_account(
|
||||
gotrue_client: &gotrue::api::Client,
|
||||
gotrue_client: gotrue::api::Client,
|
||||
pg_pool: &PgPool,
|
||||
gotrue_setting: &GoTrueSetting,
|
||||
) -> Result<GoTrueAdmin, Error> {
|
||||
let admin_email = gotrue_setting.admin_email.as_str();
|
||||
let password = gotrue_setting.admin_password.expose_secret();
|
||||
let gotrue_admin = GoTrueAdmin::new(admin_email.to_owned(), password.to_owned());
|
||||
let gotrue_admin = GoTrueAdmin::new(
|
||||
admin_email.to_owned(),
|
||||
password.to_owned(),
|
||||
gotrue_client.clone(),
|
||||
);
|
||||
|
||||
match gotrue_client
|
||||
.token(&Grant::Password(PasswordGrant {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,3 @@ pub async fn update_user(
|
|||
let metadata = params.metadata.map(|m| json!(m.into_inner()));
|
||||
Ok(database::user::update_user(pg_pool, &user_uuid, params.name, params.email, metadata).await?)
|
||||
}
|
||||
|
||||
pub async fn delete_user(pg_pool: &PgPool, user_uuid: Uuid) -> Result<(), AppResponseError> {
|
||||
Ok(database::user::delete_user(pg_pool, &user_uuid).await?)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ pub async fn invite_workspace_members(
|
|||
.begin()
|
||||
.await
|
||||
.context("Begin transaction to invite workspace members")?;
|
||||
let admin_token = gotrue_admin.token(gotrue_client).await?;
|
||||
let admin_token = gotrue_admin.token().await?;
|
||||
|
||||
let inviter_name = database::user::select_name_from_uuid(pg_pool, inviter).await?;
|
||||
let workspace_name =
|
||||
|
|
|
|||
|
|
@ -150,20 +150,23 @@ impl AppMetrics {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GoTrueAdmin {
|
||||
pub gotrue_client: gotrue::api::Client,
|
||||
pub admin_email: String,
|
||||
pub password: Secret<String>,
|
||||
}
|
||||
|
||||
impl GoTrueAdmin {
|
||||
pub fn new(admin_email: String, password: String) -> Self {
|
||||
pub fn new(admin_email: String, password: String, gotrue_client: gotrue::api::Client) -> Self {
|
||||
Self {
|
||||
admin_email,
|
||||
password: password.into(),
|
||||
gotrue_client,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn token(&self, client: &gotrue::api::Client) -> Result<String, AppError> {
|
||||
let token = client
|
||||
pub async fn token(&self) -> Result<String, AppError> {
|
||||
let token = self
|
||||
.gotrue_client
|
||||
.token(&Grant::Password(PasswordGrant {
|
||||
email: self.admin_email.clone(),
|
||||
password: self.password.expose_secret().clone(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue