From 0c142f1f678243981a98042bbfaca65738e01a67 Mon Sep 17 00:00:00 2001 From: Ruben Rosario Date: Sun, 21 Jun 2026 17:23:56 +0100 Subject: [PATCH] Auto-detect missing scopes and re-auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added token_has_all_scopes() to ApiClient — reads stored token.json and checks if all requested scopes are present. If token exists but lacks calendar.readonly, the auth popup is shown instead of silently failing in the sync engine. --- src/app.rs | 2 +- src/infrastructure/api.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 73ab7ac..8b7c939 100644 --- a/src/app.rs +++ b/src/app.rs @@ -69,7 +69,7 @@ pub enum SyncCommand { impl App { pub fn new(db: Arc, api_client: Arc, sync_tx: mpsc::Sender, _calendar_events_shared: Arc>>) -> Self { - let has_token = api_client.has_token(); + let has_token = api_client.has_token() && api_client.token_has_all_scopes(); let (auth_tx, auth_rx) = std_mpsc::channel(); let show_popup = if has_token { diff --git a/src/infrastructure/api.rs b/src/infrastructure/api.rs index 16fe926..f249afc 100644 --- a/src/infrastructure/api.rs +++ b/src/infrastructure/api.rs @@ -73,6 +73,22 @@ impl ApiClient { self.token_path.exists() } + pub fn token_has_all_scopes(&self) -> bool { + let content = match std::fs::read_to_string(&self.token_path) { + Ok(c) => c, + Err(_) => return false, + }; + let value: serde_json::Value = match serde_json::from_str(&content) { + Ok(v) => v, + Err(_) => return false, + }; + let scope = match value["scope"].as_str() { + Some(s) => s, + None => return false, + }; + SCOPES.iter().all(|s| scope.contains(s)) + } + pub async fn start_and_wait_for_auth(&self) -> Result<(), ApiError> { self.authenticator.token(SCOPES).await.map_err(|e| { ApiError::Auth(format!("Authorization failed: {}", e))