From 975c9701ce422ee9c4bf18e1954ab512d78ac5e1 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Mon, 27 Apr 2026 12:53:33 -0700 Subject: [PATCH] Fix Gmail tray OAuth at login --- nixos/notifications-tray-icon.nix | 14 +++ ...ay-icon-gmail-oauth-detached-browser.patch | 114 ++++++++++++++++++ nixos/sni.nix | 2 - 3 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 nixos/patches/notifications-tray-icon-gmail-oauth-detached-browser.patch diff --git a/nixos/notifications-tray-icon.nix b/nixos/notifications-tray-icon.nix index a1c89230..2678a435 100644 --- a/nixos/notifications-tray-icon.nix +++ b/nixos/notifications-tray-icon.nix @@ -2,6 +2,20 @@ makeEnable config "myModules.notifications-tray-icon" true { nixpkgs.overlays = [ inputs.notifications-tray-icon.overlays.default + (final: prev: { + haskellPackages = prev.haskellPackages.override (old: { + overrides = final.lib.composeExtensions (old.overrides or (_: _: {})) + (hself: hsuper: { + notifications-tray-icon = final.haskell.lib.overrideCabal + hsuper.notifications-tray-icon + (oldAttrs: { + patches = (oldAttrs.patches or []) ++ [ + ./patches/notifications-tray-icon-gmail-oauth-detached-browser.patch + ]; + }); + }); + }); + }) ]; home-manager.users.imalison = { config, ... }: let diff --git a/nixos/patches/notifications-tray-icon-gmail-oauth-detached-browser.patch b/nixos/patches/notifications-tray-icon-gmail-oauth-detached-browser.patch new file mode 100644 index 00000000..cf6e2be2 --- /dev/null +++ b/nixos/patches/notifications-tray-icon-gmail-oauth-detached-browser.patch @@ -0,0 +1,114 @@ +diff --git a/src/StatusNotifier/Item/Notifications/Gmail.hs b/src/StatusNotifier/Item/Notifications/Gmail.hs +index 6b8206b..cd5834f 100644 +--- a/src/StatusNotifier/Item/Notifications/Gmail.hs ++++ b/src/StatusNotifier/Item/Notifications/Gmail.hs +@@ -12,7 +12,7 @@ module StatusNotifier.Item.Notifications.Gmail + ) where + + import Control.Concurrent +-import Control.Concurrent.Async (race) ++import Control.Concurrent.Async (race, wait, withAsync) + import Control.Concurrent.MVar as MV + import Control.Exception (SomeException, bracket, try) + import Control.Monad +@@ -76,11 +76,13 @@ import Network.HTTP.Conduit (newManager, tlsManagerSettings) + import qualified Network.HTTP.Client as Client + import Network.Socket + import Network.Socket.ByteString (recv, sendAll) ++import Network.HTTP.Types.URI (urlDecode, urlEncode) + import StatusNotifier.Item.Notifications.Util + import System.Directory (createDirectoryIfMissing, doesFileExist, getXdgDirectory, XdgDirectory(..)) + import System.FilePath (()) + import System.IO (hFlush, stdout) + import System.Log.Logger ++import qualified System.Process as P + import Text.Printf + + -- | Configuration for the Gmail notifier. +@@ -130,14 +132,23 @@ loopbackAuthURL :: OAuthClient -> Int -> T.Text + loopbackAuthURL client port = + accountsURL + <> "?response_type=code" +- <> "&client_id=" <> toQueryParam (_clientId client) +- <> "&redirect_uri=" <> loopbackRedirectURI port ++ <> "&client_id=" <> urlEncodeText (toQueryParam (_clientId client)) ++ <> "&redirect_uri=" <> urlEncodeText (loopbackRedirectURI port) + <> "&scope=" <> T.decodeUtf8 (queryEncodeScopes (scopeVals (Proxy :: Proxy '[Gmail'Modify]))) + <> "&access_type=offline" + + loopbackRedirectURI :: Int -> T.Text + loopbackRedirectURI port = "http://localhost:" <> T.pack (show port) + ++urlEncodeText :: T.Text -> T.Text ++urlEncodeText = T.decodeUtf8 . urlEncode True . T.encodeUtf8 ++ ++urlDecodeText :: BS8.ByteString -> T.Text ++urlDecodeText = T.decodeUtf8 . urlDecode True ++ ++formParam :: T.Text -> T.Text -> T.Text ++formParam key value = key <> "=" <> urlEncodeText value ++ + -- | Exchange an authorization code for a token using the loopback redirect URI. + exchangeCodeLoopback + :: OAuthClient -> OAuthCode s -> Int +@@ -146,14 +157,31 @@ exchangeCodeLoopback + exchangeCodeLoopback client code port = + refreshRequest $ + tokenRequest +- { Client.requestBody = textBody $ +- "grant_type=authorization_code" +- <> "&client_id=" <> toQueryParam (_clientId client) +- <> "&client_secret=" <> toQueryParam (_clientSecret client) +- <> "&code=" <> toQueryParam code +- <> "&redirect_uri=" <> loopbackRedirectURI port ++ { Client.requestBody = textBody $ T.intercalate "&" ++ [ formParam "grant_type" "authorization_code" ++ , formParam "client_id" (toQueryParam (_clientId client)) ++ , formParam "client_secret" (toQueryParam (_clientSecret client)) ++ , formParam "code" (toQueryParam code) ++ , formParam "redirect_uri" (loopbackRedirectURI port) ++ ] + } + ++-- | Open the authorization URL without waiting for the browser process. ++-- ++-- xdg-open can remain parented to the browser on some desktop sessions. If we ++-- wait for it, the OAuth redirect listener never starts and Google redirects to ++-- a closed localhost port. ++openBrowserDetached :: T.Text -> IO () ++openBrowserDetached url = do ++ let process = ++ (P.proc "/usr/bin/env" ["xdg-open", T.unpack url]) ++ { P.std_in = P.NoStream ++ , P.std_out = P.NoStream ++ , P.std_err = P.NoStream ++ , P.close_fds = True ++ } ++ void $ P.createProcess process ++ + -- | Start a temporary HTTP server, wait for the OAuth redirect, extract the code. + waitForOAuthRedirect :: Int -> IO T.Text + waitForOAuthRedirect port = do +@@ -200,7 +228,7 @@ extractCodeFromRequest req = do + -- | Parse query parameters from a query string like "code=xxx&scope=yyy" + parseQueryParams :: BS8.ByteString -> [(BS8.ByteString, T.Text)] + parseQueryParams qs = +- [ (key, T.decodeUtf8 val) ++ [ (key, urlDecodeText val) + | part <- BS8.split '&' qs + , let (key, rest) = BS8.break (== '=') part + val = BS8.drop 1 rest -- drop the '=' +@@ -233,9 +261,11 @@ setupGmailEnv config@GmailConfig{..} = do + authUrl = loopbackAuthURL client port + gmailLog INFO "Starting OAuth2 loopback flow" + putStrLn "Opening browser for Gmail authorization..." +- void $ xdgOpen [T.unpack authUrl] + putStrLn $ "Waiting for authorization redirect on port " ++ show port ++ "..." +- code <- waitForOAuthRedirect port ++ code <- withAsync (waitForOAuthRedirect port) $ \redirectWaiter -> do ++ threadDelay 100000 ++ openBrowserDetached authUrl ++ wait redirectWaiter + gmailLog INFO "Authorization code received" + + -- Exchange the code ourselves (with matching loopback redirect_uri) diff --git a/nixos/sni.nix b/nixos/sni.nix index 1b44eea3..9f7ef9ea 100644 --- a/nixos/sni.nix +++ b/nixos/sni.nix @@ -30,8 +30,6 @@ makeEnable config "myModules.sni" true { }; }; - services.blueman-applet.enable = true; - services.kdeconnect = { enable = true; indicator = true;