mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
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 <studiokeys@blissroms.org>
This commit is contained in:
parent
1dffdd05f2
commit
a92ee9c9b3
1 changed files with 34 additions and 9 deletions
|
|
@ -29,11 +29,11 @@ private enum SpotifyLoginConstants {
|
||||||
)!
|
)!
|
||||||
static let cookieDomainSuffix = "spotify.com"
|
static let cookieDomainSuffix = "spotify.com"
|
||||||
static let cookieName = "sp_dc"
|
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 =
|
static let userAgent =
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
|
"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"
|
+ "(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")
|
private let spotifyLoginLogger = os.Logger(subsystem: "com.Ebullioscopic.Atoll", category: "SpotifyLogin")
|
||||||
|
|
@ -43,7 +43,8 @@ struct SpotifyLoginSheet: View {
|
||||||
|
|
||||||
let onCapture: (String) -> Void
|
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
|
@State private var didCapture = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
@ -124,10 +125,6 @@ struct SpotifyLoginWebView: NSViewRepresentable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class FirstClickWebView: WKWebView {
|
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 }
|
override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,8 +175,6 @@ struct SpotifyLoginWebView: NSViewRepresentable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - WKHTTPCookieStoreObserver
|
|
||||||
|
|
||||||
func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
|
func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
|
||||||
cookieStore.getAllCookies { [weak self] cookies in
|
cookieStore.getAllCookies { [weak self] cookies in
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
|
|
@ -190,6 +185,30 @@ struct SpotifyLoginWebView: NSViewRepresentable {
|
||||||
|
|
||||||
// MARK: - WKNavigationDelegate
|
// 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!) {
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||||
let host = webView.url?.host ?? ""
|
let host = webView.url?.host ?? ""
|
||||||
spotifyLoginLogger.debug("didFinish host=\(host, privacy: .public)")
|
spotifyLoginLogger.debug("didFinish host=\(host, privacy: .public)")
|
||||||
|
|
@ -228,6 +247,12 @@ struct SpotifyLoginWebView: NSViewRepresentable {
|
||||||
windowFeatures: WKWindowFeatures
|
windowFeatures: WKWindowFeatures
|
||||||
) -> WKWebView? {
|
) -> WKWebView? {
|
||||||
if navigationAction.targetFrame == nil, let url = navigationAction.request.url {
|
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))
|
webView.load(URLRequest(url: url))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue