From a92ee9c9b33539f4749b8006f298bf7be4f1fac2 Mon Sep 17 00:00:00 2001 From: Jis G Jacob Date: Thu, 14 May 2026 08:35:41 +0530 Subject: [PATCH] Atoll: block embedded Google OAuth in Spotify login sheet Google's "secure browsers" policy refuses OAuth inside any WKWebView regardless of UA, so signing into Spotify with a Google-linked account in the in-app sheet dead-ended on Google's "browser may be insecure" page. Intercept navigations to accounts.google.com / accounts.youtube.com in both decidePolicyFor and the createWebViewWith popup path, redirect back to Spotify's login screen, and surface a status message pointing users at email/password/Apple/Facebook sign-in or the existing "Open in Browser" escape hatch for Google accounts. Update the sheet's initial banner so the limitation is visible before the user tries it. Signed-off-by: Jis G Jacob --- .../Settings/SpotifyLoginSheet.swift | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/DynamicIsland/components/Settings/SpotifyLoginSheet.swift b/DynamicIsland/components/Settings/SpotifyLoginSheet.swift index 7c326f5..7eecc8d 100644 --- a/DynamicIsland/components/Settings/SpotifyLoginSheet.swift +++ b/DynamicIsland/components/Settings/SpotifyLoginSheet.swift @@ -29,11 +29,11 @@ private enum SpotifyLoginConstants { )! static let cookieDomainSuffix = "spotify.com" static let cookieName = "sp_dc" - // Chrome UA — Spotify's anti-bot heuristics block "Safari" WebViews more - // aggressively than they block Chromium-shaped ones in practice. static let userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" + static let blockedOAuthHostSuffixes = ["accounts.google.com", "accounts.youtube.com"] + static let googleSignInRedirectURL = URL(string: "https://accounts.spotify.com/en/login")! } private let spotifyLoginLogger = os.Logger(subsystem: "com.Ebullioscopic.Atoll", category: "SpotifyLogin") @@ -43,7 +43,8 @@ struct SpotifyLoginSheet: View { let onCapture: (String) -> Void - @State private var statusText: String = "Sign in to Spotify to finish setup." + @State private var statusText: String = + "Sign in with email & password, Apple, or Facebook. Google blocks in-app sign-in — use “Open in Browser” for Google accounts." @State private var didCapture = false var body: some View { @@ -124,10 +125,6 @@ struct SpotifyLoginWebView: NSViewRepresentable { } private final class FirstClickWebView: WKWebView { - // SwiftUI sheets present the WKWebView with the host window already key, but - // AppKit still treats the webview as inactive until something inside it gets - // explicit focus. Without this override the first mouseDown is consumed - // making the view first responder rather than dispatching the click. override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true } } @@ -178,8 +175,6 @@ struct SpotifyLoginWebView: NSViewRepresentable { } } - // MARK: - WKHTTPCookieStoreObserver - func cookiesDidChange(in cookieStore: WKHTTPCookieStore) { cookieStore.getAllCookies { [weak self] cookies in Task { @MainActor in @@ -190,6 +185,30 @@ struct SpotifyLoginWebView: NSViewRepresentable { // MARK: - WKNavigationDelegate + func webView( + _ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void + ) { + if let url = navigationAction.request.url, Self.isBlockedOAuthHost(url.host) { + decisionHandler(.cancel) + spotifyLoginLogger.debug( + "Blocked embedded OAuth navigation to host=\(url.host ?? "", privacy: .public)" + ) + onStatus( + "Google blocks in-app sign-in. Use email & password, Apple, or tap “Open in Browser” to sign in there and paste sp_dc in Settings." + ) + webView.load(URLRequest(url: SpotifyLoginConstants.googleSignInRedirectURL)) + return + } + decisionHandler(.allow) + } + + private static func isBlockedOAuthHost(_ host: String?) -> Bool { + guard let host else { return false } + return SpotifyLoginConstants.blockedOAuthHostSuffixes.contains { host.hasSuffix($0) } + } + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { let host = webView.url?.host ?? "" spotifyLoginLogger.debug("didFinish host=\(host, privacy: .public)") @@ -228,6 +247,12 @@ struct SpotifyLoginWebView: NSViewRepresentable { windowFeatures: WKWindowFeatures ) -> WKWebView? { if navigationAction.targetFrame == nil, let url = navigationAction.request.url { + if Self.isBlockedOAuthHost(url.host) { + onStatus( + "Google blocks in-app sign-in. Use email & password, Apple, or tap “Open in Browser” to sign in there and paste sp_dc in Settings." + ) + return nil + } webView.load(URLRequest(url: url)) } return nil