Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: shouldDoInterceptionBasedOnUrl returns true for any valid subdomain #65

Merged
merged 1 commit into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.0] - 2024-05-07

### Breaking change

The `shouldDoInterceptionBasedOnUrl` function now returns true:
- If `sessionTokenBackendDomain` is a valid subdomain of the URL's domain. This aligns with the behavior of browsers when sending cookies to subdomains.
- Even if the ports of the URL you are querying are different compared to the `apiDomain`'s port ot the `sessionTokenBackendDomain` port (as long as the hostname is the same, or a subdomain of the `sessionTokenBackendDomain`): https://github.com/supertokens/supertokens-website/issues/217

## [0.2.7] - 2024-03-14

- New FDI version support: 1.19
Expand Down
2 changes: 1 addition & 1 deletion SuperTokensIOS.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Pod::Spec.new do |s|
s.name = 'SuperTokensIOS'
s.version = "0.2.7"
s.version = "0.3.0"
s.summary = 'SuperTokens SDK for using login and session management functionality in iOS apps'

# This description is used to generate tags and improve search results.
Expand Down
83 changes: 39 additions & 44 deletions SuperTokensIOS/Classes/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,44 +63,38 @@ class NormalisedInputType {
}

internal static func sessionScopeHelper(sessionScope: String) throws -> String {
var trimmedSessionScope = sessionScope.trim()
var trimmedSessionScope = sessionScope.trim().lowercased()

if trimmedSessionScope.starts(with: ".") {
trimmedSessionScope = trimmedSessionScope.substring(fromIndex: 1)
}

if !trimmedSessionScope.starts(with: "http://") && !trimmedSessionScope.starts(with: "https://") {
trimmedSessionScope = "http://" + trimmedSessionScope
}

do {
guard let url: URL = URL(string: trimmedSessionScope), let host: String = url.host else {
throw SDKFailableError.failableError
}

trimmedSessionScope = host

if trimmedSessionScope.starts(with: ".") {
trimmedSessionScope = trimmedSessionScope.substring(fromIndex: 1)
}

return trimmedSessionScope

return host
} catch {
throw SuperTokensError.initError(message: "Please provide a valid sessionScope")
}
}

internal static func normaliseSessionScopeOrThrowError(sessionScope: String) throws -> String {
let noDotNormalised = try sessionScopeHelper(sessionScope: sessionScope)

if noDotNormalised == "localhost" || Utils.isIpAddress(input: noDotNormalised) {
return noDotNormalised
}

if sessionScope.starts(with: ".") {
return "." + noDotNormalised
}

return noDotNormalised
}

Expand Down Expand Up @@ -156,42 +150,43 @@ class NormalisedInputType {

internal class Utils {
internal static func shouldDoInterception(toCheckURL: String, apiDomain: String, cookieDomain: String?) throws -> Bool {
let _toCheckURL: String = try NormalisedURLDomain.normaliseUrlDomainOrThrowError(input: toCheckURL)
var _apiDomain: String = apiDomain

guard let urlObj: URL = URL(string: _toCheckURL), let hostname: String = urlObj.host else {
let normalizedToCheckURL = try NormalisedURLDomain.normaliseUrlDomainOrThrowError(input: toCheckURL)
guard let urlObj = URL(string: normalizedToCheckURL), let hostname = urlObj.host else {
throw SDKFailableError.failableError
}

var domain = hostname

if cookieDomain == nil {
domain = urlObj.port == nil ? domain : domain + ":" + "\(urlObj.port!)"
_apiDomain = try NormalisedURLDomain.normaliseUrlDomainOrThrowError(input: apiDomain)

guard let apiUrlObj: URL = URL(string: _apiDomain), let apiHostName: String = apiUrlObj.host else {
var apiDomainAndInputDomainMatch = false
if !apiDomain.isEmpty {
let normalizedApiDomain = try NormalisedURLDomain.normaliseUrlDomainOrThrowError(input: apiDomain)
guard let apiUrlObj = URL(string: normalizedApiDomain), let apiHostName = apiUrlObj.host else {
throw SDKFailableError.failableError
}

return domain == (apiUrlObj.port == nil ? apiHostName : apiHostName + ":" + "\(apiUrlObj.port!)")
apiDomainAndInputDomainMatch = domain == apiHostName
}

if cookieDomain == nil || apiDomainAndInputDomainMatch {
// even if cookieDomain isn't undefined, if there is an exact match
// of api domain, ignoring the port, we return true
return apiDomainAndInputDomainMatch
} else {
var normalisedCookieDomain = try NormalisedInputType.normaliseSessionScopeOrThrowError(sessionScope: cookieDomain!)

if cookieDomain!.split(separator: ":").count > 1 {
let portString: String = String(cookieDomain!.split(separator: ":")[cookieDomain!.split(separator: ":").count - 1])

if portString.isNumeric {
normalisedCookieDomain = normalisedCookieDomain + ":" + portString
domain = urlObj.port == nil ? domain : domain + ":" + "\(urlObj.port!)"
}
}

if cookieDomain!.starts(with: ".") {
return ("." + domain).hasSuffix(normalisedCookieDomain)
} else {
return domain == normalisedCookieDomain
let normalisedSessionDomain = try NormalisedInputType.normaliseSessionScopeOrThrowError(sessionScope: cookieDomain!)
// Using the matchesDomainOrSubdomain function to determine match
return matchesDomainOrSubdomain(domain: domain, str: normalisedSessionDomain)
}
}

private static func matchesDomainOrSubdomain(domain: String, str: String) -> Bool {
let parts = domain.split(separator: ".").map(String.init)

for i in 0..<parts.count {
let subdomainCandidate = parts[i...].joined(separator: ".")
if subdomainCandidate == str || ".\(subdomainCandidate)" == str {
return true
}
}

return false
}

internal static func isIpAddress(input: String) -> Bool {
Expand Down
2 changes: 1 addition & 1 deletion SuperTokensIOS/Classes/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ import Foundation

internal class Version {
static let supported_fdi: [String] = ["1.16", "1.17", "1.18", "1.19"]
static let sdkVersion = "0.2.7"
static let sdkVersion = "0.3.0"
}
50 changes: 40 additions & 10 deletions testHelpers/testapp/Tests/config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class ConfigTests: XCTestCase {
);
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost:3000", apiDomain: "localhost:3000", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://localhost:3000", apiDomain: "https://localhost:3000", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://localhost:3000", apiDomain: "https://localhost:3001", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://localhost:3000", apiDomain: "http://localhost:3001", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost:3000", apiDomain: "localhost:3001", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://localhost:3000", apiDomain: "http://localhost:3000", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost:3000", apiDomain: "https://localhost:3000", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost", apiDomain: "https://localhost", cookieDomain: nil));
Expand All @@ -31,19 +34,27 @@ class ConfigTests: XCTestCase {
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "127.0.0.1:3000", apiDomain: "https://127.0.0.1:3000", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://127.0.0.1:3000", apiDomain: "https://127.0.0.1:3000", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://127.0.0.1", apiDomain: "https://127.0.0.1", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://localhost.org", apiDomain: "localhost.org", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://localhost.org", apiDomain: "http://localhost.org", cookieDomain: nil));

// true cases with cookieDomain
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "api.example.com", apiDomain: "", cookieDomain: "api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://api.example.com", apiDomain: "", cookieDomain: "http://api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "api.example.com", apiDomain: "", cookieDomain: ".example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "api.example.com", apiDomain: "", cookieDomain: "example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com", apiDomain: "", cookieDomain: "http://api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com", apiDomain: "", cookieDomain: "https://api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com", apiDomain: "", cookieDomain: ".sub.api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com", apiDomain: "", cookieDomain: "sub.api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com", apiDomain: "", cookieDomain: ".api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com", apiDomain: "", cookieDomain: "api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com", apiDomain: "", cookieDomain: ".example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com", apiDomain: "", cookieDomain: "example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com:3000", apiDomain: "", cookieDomain: ".example.com:3000"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com:3000", apiDomain: "", cookieDomain: ".example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com:3000", apiDomain: "", cookieDomain: "https://sub.api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com:3000", apiDomain: "", cookieDomain: ".api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com:3000", apiDomain: "", cookieDomain: "api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost:3000", apiDomain: "", cookieDomain: "localhost:3000"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://localhost:3000", apiDomain: "", cookieDomain: ".localhost:3000"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost", apiDomain: "", cookieDomain: "localhost"));
Expand All @@ -59,27 +70,46 @@ class ConfigTests: XCTestCase {
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub1.api.example.co.uk:3000", apiDomain: "", cookieDomain: ".api.example.co.uk"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://api.example.co.uk:3000", apiDomain: "", cookieDomain: ".api.example.co.uk"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://api.example.co.uk:3000", apiDomain: "", cookieDomain: "api.example.co.uk"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com", apiDomain: "", cookieDomain: "example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com", apiDomain: "", cookieDomain: ".sub.api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com", apiDomain: "", cookieDomain: "sub.api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com", apiDomain: "", cookieDomain: "api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com", apiDomain: "", cookieDomain: "example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com:3000", apiDomain: "", cookieDomain: "example.com:3000"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com", apiDomain: "", cookieDomain: "api.example.com"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost:3000", apiDomain: "localhost:8080", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost:3001", apiDomain: "localhost", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com:3002", apiDomain: "https://api.example.com:3001", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://localhost.org", apiDomain: "localhost.org:2000", cookieDomain: nil));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://localhost.org", apiDomain: "localhost", cookieDomain: "localhost.org"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost", apiDomain: "localhost", cookieDomain: "localhost.org"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost", apiDomain: "", cookieDomain: "localhost:8080"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://localhost:80", apiDomain: "", cookieDomain: "localhost:8080"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "localhost:3000", apiDomain: "", cookieDomain: "localhost:8080"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com:3000", apiDomain: "", cookieDomain: ".example.com:3001"));
XCTAssertTrue(try Utils.shouldDoInterception(toCheckURL: "http://127.0.0.1:3000", apiDomain: "", cookieDomain: "https://127.0.0.1:3010"));

// false cases with api
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "localhost:3001", apiDomain: "localhost:3000", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "localhost:3001", apiDomain: "example.com", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "localhost:3001", apiDomain: "localhost", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "localhost", apiDomain: "localhost.org", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "google.com", apiDomain: "localhost.org", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "http://google.com", apiDomain: "localhost.org", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://google.com", apiDomain: "localhost.org", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://google.com:8080", apiDomain: "localhost.org", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com", apiDomain: "https://a.api.example.com:3000", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://example.com", apiDomain: "https://api.example.com", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com", apiDomain: "https://a.api.example.com", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com", apiDomain: "https://example.com", cookieDomain: nil)));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://example.com:3001", apiDomain: "https://api.example.com:3001", cookieDomain: nil)));
XCTAssertTrue(
!(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com:3002", apiDomain: "https://api.example.com:3001", cookieDomain: nil))
);

// false cases with cookieDomain
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com:3000", apiDomain: "", cookieDomain: ".example.com:3001")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com:3000", apiDomain: "", cookieDomain: "example.com")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "localhost", apiDomain: "", cookieDomain: "localhost.org")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "google.com", apiDomain: "", cookieDomain: "localhost.org")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "http://google.com", apiDomain: "", cookieDomain: "localhost.org")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://google.com", apiDomain: "", cookieDomain: "localhost.org")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://google.com:8080", apiDomain: "", cookieDomain: "localhost.org")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://api.example.com:3000", apiDomain: "", cookieDomain: ".a.api.example.com")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.com:3000", apiDomain: "", cookieDomain: "localhost")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "http://127.0.0.1:3000", apiDomain: "", cookieDomain: "https://127.0.0.1:3010")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.co.uk:3000", apiDomain: "", cookieDomain: "api.example.co.uk")));
XCTAssertTrue(!(try Utils.shouldDoInterception(toCheckURL: "https://sub.api.example.co.uk", apiDomain: "", cookieDomain: "api.example.co.uk")));

// errors in input
do {
Expand Down
Loading