Compare commits

...
Author SHA1 Message Date
Brandon Thomas 06260277f7 purge artcraft credentials command 2025-09-27 13:18:04 -04:00
13 changed files with 181 additions and 4 deletions
@@ -0,0 +1 @@
mod reset_artcraft_command;
@@ -0,0 +1,34 @@
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageSuccessResponse;
use crate::core::commands::response::shorthand::ResponseOrErrorMessage;
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
use crate::core::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use crate::services::storyteller::windows::clear_main_webview_window_storyteller_cookies::clear_main_webview_window_storyteller_cookies;
use anyhow::anyhow;
use errors::AnyhowResult;
use log::{error, info};
use serde_derive::{Deserialize, Serialize};
use tauri::{AppHandle, State};
#[derive(Serialize)]
pub struct ResetArtcraftCommandResponse {
pub success: bool,
}
impl SerializeMarker for ResetArtcraftCommandResponse {}
#[tauri::command]
pub async fn reset_artcraft_command(
app: AppHandle,
) -> ResponseOrErrorMessage<ResetArtcraftCommandResponse> {
info!("reset_artcraft_command called");
// TODO: Reset local vs. production
Ok(ResetArtcraftCommandResponse {
success: true,
}.into())
}
@@ -1,4 +1,5 @@
pub mod app_preferences;
pub mod app_state;
pub mod enqueue;
pub mod flip_image;
pub mod get_app_info_command;
@@ -5,14 +5,13 @@ use crate::core::state::window::main_window_size::MainWindowSize;
use crate::core::threads::main_window_thread::persist_storyteller_cookies_task::persist_storyteller_cookies_task;
use crate::core::threads::main_window_thread::persist_window_position_task::persist_window_position_task;
use crate::core::threads::main_window_thread::persist_window_resize_task::persist_window_resize_task;
use crate::core::windows::main_window::constants::MAIN_WINDOW_NAME;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use errors::AnyhowResult;
use log::{error, info, warn};
use memory_store::clone_slot::CloneSlot;
use tauri::{AppHandle, Manager, Window};
const MAIN_WINDOW_NAME : &str = "main";
pub async fn main_window_thread(
app: AppHandle,
app_data_root: AppDataRoot,
@@ -13,8 +13,6 @@ use tauri::{AppHandle, Manager};
use tauri_plugin_http::reqwest_cookie_store::CookieStore;
use tauri_plugin_http::Http;
const MAIN_WEBVIEW_NAME: &str = "main";
const STORYTELLER_ROOT_COOKIE_URL_STR: &str = "https://api.storyteller.ai/";
static STORYTELLER_ROOT_COOKIE_URL: Lazy<Url> = Lazy::new(|| {
@@ -3,6 +3,9 @@
/// This is the Artcraft application UX itself.
pub const MAIN_WINDOW_NAME : &str = "main";
/// This is the name of the main window's main webview.
pub const MAIN_WEBVIEW_NAME: &str = "main";
// TODO: handle pixel density, eg. retina displays
/// This is the minimum width to resize the main window in an automated fashion
pub const MAIN_WINDOW_MIN_WIDTH : u32 = 800;
+2
View File
@@ -48,6 +48,7 @@ use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
use crate::services::sora::threads::sora_task_polling_thread::sora_task_polling_thread;
use crate::services::storyteller::commands::storyteller_get_credits_command::storyteller_get_credits_command;
use crate::services::storyteller::commands::storyteller_get_subscription_command::storyteller_get_subscription_command;
use crate::services::storyteller::commands::storyteller_purge_credentials_command::storyteller_purge_credentials_command;
use crate::services::storyteller::commands::stripe_checkout::storyteller_open_credits_purchase_command::storyteller_open_credits_purchase_command;
use crate::services::storyteller::commands::stripe_checkout::storyteller_open_subscription_purchase_command::storyteller_open_subscription_purchase_command;
use crate::services::storyteller::commands::stripe_customer_portal::storyteller_open_customer_portal_cancel_plan_command::storyteller_open_customer_portal_cancel_plan_command;
@@ -208,6 +209,7 @@ pub fn run() {
storyteller_open_customer_portal_switch_plan_command,
storyteller_open_customer_portal_update_payment_method_command,
storyteller_open_subscription_purchase_command,
storyteller_purge_credentials_command,
update_app_preferences_command,
]);
@@ -1,3 +1,4 @@
pub mod storyteller_purge_credentials_command;
pub mod storyteller_get_credits_command;
pub mod storyteller_get_subscription_command;
pub mod stripe_checkout;
@@ -0,0 +1,56 @@
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageSuccessResponse;
use crate::core::commands::response::shorthand::ResponseOrErrorMessage;
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
use crate::core::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use crate::services::storyteller::windows::clear_main_webview_window_storyteller_cookies::clear_main_webview_window_storyteller_cookies;
use anyhow::anyhow;
use errors::AnyhowResult;
use log::{error, info};
use serde_derive::{Deserialize, Serialize};
use tauri::{AppHandle, State};
#[derive(Serialize)]
pub struct PurgeStorytellerCredentialsCommandResponse {
pub success: bool,
}
impl SerializeMarker for PurgeStorytellerCredentialsCommandResponse {}
#[tauri::command]
pub async fn storyteller_purge_credentials_command(
app: AppHandle,
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
) -> ResponseOrErrorMessage<PurgeStorytellerCredentialsCommandResponse> {
info!("storyteller_purge_credentials_command called");
reset(&app, &storyteller_creds_manager)
.await
.map_err(|err| {
error!("Error purging credentials: {:?}", err);
format!("Error purging credentials: {:?}", err)
})?;
Ok(PurgeStorytellerCredentialsCommandResponse {
success: true,
}.into())
}
async fn reset(
app: &AppHandle,
storyteller_creds_manager: &StorytellerCredentialManager
) -> AnyhowResult<()> {
clear_main_webview_window_storyteller_cookies(&app)?;
storyteller_creds_manager.clear_credentials()?;
storyteller_creds_manager.delete_persisted_copies_on_disk()?;
// NB: Twice to be sure.
storyteller_creds_manager.clear_credentials()?;
storyteller_creds_manager.delete_persisted_copies_on_disk()?;
Ok(())
}
@@ -78,6 +78,22 @@ impl StorytellerCredentialManager {
Ok(())
}
pub fn delete_persisted_copies_on_disk(&self) -> AnyhowResult<()> {
let creds_dir = self.app_data_root.credentials_dir();
let session_file = creds_dir.get_storyteller_session_cookie_file_path();
if session_file.exists() {
std::fs::remove_file(session_file)?;
}
let avt_file = creds_dir.get_storyteller_avt_cookie_file_path();
if avt_file.exists() {
std::fs::remove_file(avt_file)?;
}
Ok(())
}
}
fn persist_session_to_disk(session: &StorytellerSessionCookie, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
@@ -0,0 +1,59 @@
use crate::core::windows::main_window::constants::MAIN_WINDOW_NAME;
use errors::AnyhowResult;
use log::warn;
use tauri::webview::Cookie;
use tauri::{AppHandle, Manager};
pub fn clear_main_webview_window_storyteller_cookies(app: &AppHandle) -> AnyhowResult<()> {
let main_webview_window = app.get_webview_window(MAIN_WINDOW_NAME)
.ok_or_else(|| anyhow::anyhow!("Main window not found"))?;
let cookies = main_webview_window.cookies()?;
let cookies_to_delete = get_cookies_to_delete(cookies);
let mut maybe_error = None;
for cookie in cookies_to_delete {
if let Err(err) = main_webview_window.delete_cookie(cookie) {
warn!("Failed to delete cookie: {:?}", err);
maybe_error = Some(err);
}
}
if let Some(err) = maybe_error {
return Err(anyhow::anyhow!("Failed to delete some cookies. Here's one failure: {:?}", err));
}
Ok(())
}
// TODO: This will need to handle local development, too.
fn get_cookies_to_delete(cookies: Vec<Cookie>) -> Vec<Cookie> {
cookies.into_iter()
.filter(|cookie| {
// Define your filtering logic here
// For example, delete cookies with a specific name or domain
match cookie.domain() {
// storyteller.ai
Some("api.storyteller.ai") => true,
Some("storyteller.ai") => true,
Some(domain) if domain.ends_with(".storyteller.ai") => true,
// getartcraft.com
Some("api.getartcraft.com") => true,
Some("getartcraft.com") => true,
Some(domain) if domain.ends_with(".getartcraft.com") => true,
// artcraft.ai
Some("api.artcraft.ai") => true,
Some("artcraft.ai") => true,
Some(domain) if domain.ends_with(".artcraft.ai") => true,
// ignore all others
None => false,
Some(domain) => {
warn!("Ignoring cookie with unrecognized domain: {}", domain);
false
}
}
})
.collect()
}
@@ -1,2 +1,3 @@
pub mod clear_main_webview_window_storyteller_cookies;
pub mod open_storyteller_billing_window;
pub mod storyteller_billing_window_thread;
@@ -6,6 +6,7 @@ import { Button } from "@storyteller/ui-button";
import { UsersApi } from "@storyteller/api";
import { UserInfo } from "@storyteller/api";
import { useLoginModalStore } from "@storyteller/ui-login-modal";
import { invoke } from "@tauri-apps/api/core";
const usersApi = new UsersApi();
@@ -52,7 +53,12 @@ export const ArtcraftAccountBlock = ({
setIsLoggedIn(false);
setIsCheckingArtcraftSession(false);
globalAccountLogoutCallback(); // TODO: This resets the old global application state
triggerRecheck(); // Trigger modal to recheck session and potentially open
await invoke("storyteller_purge_credentials_command");
} else {
triggerRecheck(); // Open login modal instead of redirecting
}