| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- import Foundation
- enum RedditWebAuthHelper {
- static let redditHomeURL = URL(string: "https://www.reddit.com")!
- static let redditLoginURL = URL(string: "https://www.reddit.com/login/")!
- static let safariUserAgent =
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15"
- private static let oauthHosts = [
- "accounts.google.com",
- "appleid.apple.com",
- "idmsa.apple.com",
- "gsa.apple.com",
- "login.microsoftonline.com",
- "login.live.com",
- "account.live.com",
- "login.microsoft.com",
- "www.facebook.com",
- "m.facebook.com",
- "facebook.com",
- "auth0.com",
- ]
- private static let authFlowPathPrefixes = [
- "/login",
- "/register",
- "/password",
- "/account/login",
- "/account/register",
- "/account/signup",
- ]
- static func isOAuthProviderURL(_ url: URL) -> Bool {
- guard let host = url.host?.lowercased() else { return false }
- if oauthHosts.contains(where: { host == $0 || host.hasSuffix(".\($0)") }) {
- return true
- }
- if host == "google.com" || host.hasSuffix(".google.com") {
- let path = url.path.lowercased()
- return path.contains("oauth")
- || path.contains("signin")
- || path.contains("gsi")
- }
- if host == "apple.com" || host.hasSuffix(".apple.com") {
- let path = url.path.lowercased()
- return path.contains("auth")
- || path.contains("signin")
- || path.contains("sso")
- }
- if host == "facebook.com" || host.hasSuffix(".facebook.com") {
- let path = url.path.lowercased()
- return path.contains("oauth") || path.contains("login") || path.contains("dialog")
- }
- if host.hasSuffix(".auth0.com") {
- return true
- }
- return false
- }
- static func isRedditHomeURL(_ url: URL) -> Bool {
- guard isRedditURL(url) else { return false }
- let host = url.host?.lowercased() ?? ""
- let isMainSite = host == "reddit.com"
- || host == "www.reddit.com"
- || host == "new.reddit.com"
- || host == "old.reddit.com"
- guard isMainSite else { return false }
- let path = url.path.lowercased()
- return path.isEmpty || path == "/" || path == "/home"
- }
- static func isRedditURL(_ url: URL) -> Bool {
- guard let host = url.host?.lowercased() else { return false }
- let redditHosts = [
- "reddit.com",
- "www.reddit.com",
- "old.reddit.com",
- "new.reddit.com",
- "accounts.reddit.com",
- "oauth.reddit.com",
- "reddit.app.link",
- "redd.it",
- ]
- return redditHosts.contains(where: { host == $0 || host.hasSuffix(".\($0)") })
- }
- static func isAuthFlowURL(_ url: URL) -> Bool {
- guard isRedditURL(url) else { return false }
- let path = url.path.lowercased()
- return authFlowPathPrefixes.contains { prefix in
- path == prefix || path.hasPrefix("\(prefix)/")
- }
- }
- static func isCredentialLoginPage(_ url: URL) -> Bool {
- guard isRedditURL(url) else { return false }
- let path = url.path.lowercased()
- guard path == "/login" || path.hasPrefix("/login/") else { return false }
- let host = url.host?.lowercased() ?? ""
- return host == "www.reddit.com" || host == "reddit.com" || host == "new.reddit.com"
- }
- static func isAccountsRedditHost(_ url: URL) -> Bool {
- url.host?.lowercased() == "accounts.reddit.com"
- }
- /// Auth subframes to accounts.reddit.com are required on the modern login page.
- /// Only redirect subframe auth loads when the main frame is not already on login.
- static func shouldRedirectAuthSubframe(mainFrameURL: URL?) -> Bool {
- guard let mainFrameURL else { return true }
- if isCredentialLoginPage(mainFrameURL) { return false }
- return !isAuthFlowURL(mainFrameURL)
- }
- static func looksLikeLoggedInURL(_ url: URL) -> Bool {
- guard isRedditURL(url) else { return false }
- if isAuthFlowURL(url) { return false }
- let path = url.path.lowercased()
- if url.host?.lowercased() == "accounts.reddit.com" {
- if path.contains("/oauth") || path.contains("/authorize") || path.contains("/login") {
- return false
- }
- return path.isEmpty || path == "/" || path.hasPrefix("/api/")
- }
- return true
- }
- /// Returns true when the OAuth completion panel can close and hand off to the main WebView.
- static func shouldCompleteOAuthSignIn(for url: URL) -> Bool {
- guard isRedditURL(url) else { return false }
- if isAuthFlowURL(url) { return false }
- let host = url.host?.lowercased() ?? ""
- let path = url.path.lowercased()
- if host == "accounts.reddit.com" {
- if path.contains("/oauth")
- || path.contains("/authorize")
- || path.contains("/login")
- || path.hasPrefix("/api/") {
- return false
- }
- return path.isEmpty || path == "/"
- }
- if host == "www.reddit.com"
- || host == "reddit.com"
- || host == "new.reddit.com"
- || host == "old.reddit.com" {
- return true
- }
- return looksLikeLoggedInURL(url)
- }
- static func oauthCallbackHost(for url: URL) -> String {
- if let host = parsedRedirectHost(from: url) {
- return host
- }
- return "accounts.reddit.com"
- }
- private static func parsedRedirectHost(from url: URL) -> String? {
- guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
- return nil
- }
- let redirectValue = components.queryItems?.first(where: {
- $0.name == "redirect_uri" || $0.name == "redirect_url" || $0.name == "redirect"
- })?.value
- guard let redirectValue else { return nil }
- let decoded = redirectValue.removingPercentEncoding ?? redirectValue
- if let host = hostFromRedirectString(decoded) {
- return host
- }
- if !decoded.contains("://"), let host = hostFromRedirectString("https://\(decoded)") {
- return host
- }
- return nil
- }
- private static func hostFromRedirectString(_ value: String) -> String? {
- guard let redirectURL = URL(string: value),
- let host = redirectURL.host?.lowercased(),
- isRedditURL(redirectURL) else {
- return nil
- }
- return host
- }
- }
|