gephyr 1.16.18

Gephyr is a headless local AI relay/proxy API handling OpenAI, Claude, and Gemini-compatible APIs
Documentation
use crate::models::{Account, TokenData};
use crate::modules::auth::{account, oauth, oauth_server};
use crate::modules::system::{logger, quota};

pub struct AccountService {
    pub integration: crate::modules::system::integration::SystemManager,
}

impl AccountService {
    pub fn new(integration: crate::modules::system::integration::SystemManager) -> Self {
        Self { integration }
    }
    pub async fn add_account(&self, refresh_token: &str) -> Result<Account, String> {
        let temp_account_id = uuid::Uuid::new_v4().to_string();
        let (token_res, identity) =
            oauth::refresh_and_verify_identity(refresh_token, Some(&temp_account_id)).await?;
        let email = identity.email;
        let display_name = identity.name;
        let google_sub = identity.google_sub;
        let project_id =
            crate::proxy::project_resolver::fetch_project_id(&token_res.access_token, None)
                .await
                .ok();
        let token = TokenData::new(
            token_res.access_token.clone(),
            refresh_token.to_string(),
            token_res.expires_in,
            Some(email.clone()),
            project_id,
            None,
        );
        let mut account = account::upsert_account(email.clone(), display_name, token, google_sub)?;
        let email_for_log = account.email.clone();
        let access_token = token_res.access_token.clone();
        match quota::fetch_quota(&access_token, &email_for_log, Some(&account.id)).await {
            Ok((quota_data, new_project_id)) => {
                account.quota = Some(quota_data);
                if let Some(pid) = new_project_id {
                    account.token.project_id = Some(pid);
                }
                if let Err(e) = account::save_account(&account) {
                    logger::log_warn(&format!(
                        "[Service] Failed to save quota for {}: {}",
                        email_for_log, e
                    ));
                } else {
                    logger::log_info(&format!(
                        "[Service] Fetched quota for new account: {}",
                        email_for_log
                    ));
                }
            }
            Err(e) => {
                logger::log_warn(&format!(
                    "[Service] Failed to fetch quota for {}: {}",
                    email_for_log, e
                ));
            }
        }

        logger::log_info(&format!(
            "[Service] Added/Updated account: {}",
            account.email
        ));
        Self::spawn_google_mimic_flow(
            token_res.access_token.clone(),
            account.id.clone(),
            account.token.project_id.clone(),
        );
        Ok(account)
    }
    pub fn delete_account(&self, account_id: &str) -> Result<(), String> {
        account::delete_account(account_id)?;
        self.integration.refresh_runtime_state();
        Ok(())
    }
    pub async fn switch_account(&self, account_id: &str) -> Result<(), String> {
        account::switch_account(account_id, &self.integration).await
    }
    pub fn list_accounts(&self) -> Result<Vec<Account>, String> {
        account::list_accounts()
    }
    pub fn get_current_id(&self) -> Result<Option<String>, String> {
        account::get_current_account_id()
    }

    pub async fn logout_account(
        &self,
        account_id: &str,
        revoke_remote: bool,
    ) -> Result<(), String> {
        account::logout_account(account_id, revoke_remote).await?;
        self.integration.refresh_runtime_state();
        Ok(())
    }

    pub async fn prepare_oauth_url(&self) -> Result<String, String> {
        oauth_server::prepare_oauth_url().await
    }

    pub async fn start_oauth_login(&self) -> Result<Account, String> {
        let token_res = oauth_server::start_oauth_flow().await?;
        self.process_oauth_token(token_res).await
    }

    pub async fn complete_oauth_login(&self) -> Result<Account, String> {
        let token_res = oauth_server::complete_oauth_flow().await?;
        self.process_oauth_token(token_res).await
    }

    pub fn cancel_oauth_login(&self) {
        oauth_server::cancel_oauth_flow();
    }

    pub async fn submit_oauth_code(
        &self,
        code: String,
        state: Option<String>,
    ) -> Result<(), String> {
        oauth_server::submit_oauth_code(code, state).await
    }

    async fn process_oauth_token(
        &self,
        token_res: oauth::TokenResponse,
    ) -> Result<Account, String> {
        let refresh_token = token_res.refresh_token.ok_or_else(|| {
            "Refresh Token not found. Please revoke permissions and try again.".to_string()
        })?;
        let temp_account_id = uuid::Uuid::new_v4().to_string();
        let access_token = token_res.access_token.clone();
        let expires_in = token_res.expires_in;
        let (email, display_name, google_sub) = Self::resolve_identity_from_google(
            &access_token,
            token_res.id_token.as_deref(),
            Some(&temp_account_id),
        )
        .await?;
        let project_id =
            crate::proxy::project_resolver::fetch_project_id(&token_res.access_token, None)
                .await
                .ok();

        let token_data = crate::models::TokenData::new(
            access_token,
            refresh_token,
            expires_in,
            Some(email.clone()),
            project_id,
            None,
        );

        let account = account::upsert_account(email, display_name, token_data, google_sub)?;
        self.integration.refresh_runtime_state();
        Self::spawn_google_mimic_flow(
            token_res.access_token,
            account.id.clone(),
            account.token.project_id.clone(),
        );

        Ok(account)
    }

    async fn resolve_identity_from_google(
        access_token: &str,
        raw_id_token: Option<&str>,
        account_id: Option<&str>,
    ) -> Result<(String, Option<String>, Option<String>), String> {
        let identity = oauth::verify_identity(access_token, raw_id_token, account_id).await?;
        Ok((identity.email, identity.name, identity.google_sub))
    }

    fn spawn_google_mimic_flow(
        access_token: String,
        account_id: String,
        project_id: Option<String>,
    ) {
        if let Ok(handle) = tokio::runtime::Handle::try_current() {
            handle.spawn(async move {
                let _ = crate::proxy::google::mimic_flow::run_auth_event_mimic_flow(
                    &access_token,
                    Some(&account_id),
                    project_id.as_deref(),
                )
                .await;
            });
        } else {
            logger::log_warn("[Service] Skipping google mimic flow spawn: no async runtime");
        }
    }
}