Skip to content

Commit dde24d4

Browse files
committed
Amp: detect login redirects and fail fast
Extracted from #324 (thanks @JosephDoUrden).
1 parent bdaf139 commit dde24d4

2 files changed

Lines changed: 62 additions & 0 deletions

File tree

Sources/CodexBarCore/Providers/Amp/AmpUsageFetcher.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,9 @@ public struct AmpUsageFetcher: Sendable {
261261
if httpResponse.statusCode == 401 || httpResponse.statusCode == 403 {
262262
throw AmpUsageError.invalidCredentials
263263
}
264+
if diagnostics.detectedLoginRedirect {
265+
throw AmpUsageError.invalidCredentials
266+
}
264267
throw AmpUsageError.networkError("HTTP \(httpResponse.statusCode)")
265268
}
266269

@@ -277,6 +280,7 @@ public struct AmpUsageFetcher: Sendable {
277280
private let cookieHeader: String
278281
private let logger: ((String) -> Void)?
279282
var redirects: [String] = []
283+
private(set) var detectedLoginRedirect = false
280284

281285
init(cookieHeader: String, logger: ((String) -> Void)?) {
282286
self.cookieHeader = cookieHeader
@@ -293,6 +297,16 @@ public struct AmpUsageFetcher: Sendable {
293297
let from = response.url?.absoluteString ?? "unknown"
294298
let to = request.url?.absoluteString ?? "unknown"
295299
self.redirects.append("\(response.statusCode) \(from) -> \(to)")
300+
301+
if let toURL = request.url, AmpUsageFetcher.isLoginRedirect(toURL) {
302+
if let logger {
303+
logger("[amp] Detected login redirect, aborting (invalid session)")
304+
}
305+
self.detectedLoginRedirect = true
306+
completionHandler(nil)
307+
return
308+
}
309+
296310
var updated = request
297311
if AmpUsageFetcher.shouldAttachCookie(to: request.url), !self.cookieHeader.isEmpty {
298312
updated.setValue(self.cookieHeader, forHTTPHeaderField: "Cookie")
@@ -364,4 +378,25 @@ public struct AmpUsageFetcher: Sendable {
364378
if host == "ampcode.com" || host == "www.ampcode.com" { return true }
365379
return host.hasSuffix(".ampcode.com")
366380
}
381+
382+
static func isLoginRedirect(_ url: URL) -> Bool {
383+
guard self.shouldAttachCookie(to: url) else { return false }
384+
385+
let path = url.path.lowercased()
386+
let components = path.split(separator: "/").map(String.init)
387+
if components.contains("login") { return true }
388+
if components.contains("signin") { return true }
389+
if components.contains("sign-in") { return true }
390+
391+
// Amp currently redirects to /auth/sign-in?returnTo=... when session is invalid. Keep this slightly broader
392+
// than one exact path so we keep working if Amp changes auth routes.
393+
if components.contains("auth") {
394+
let query = url.query?.lowercased() ?? ""
395+
if query.contains("returnto=") { return true }
396+
if query.contains("redirect=") { return true }
397+
if query.contains("redirectto=") { return true }
398+
}
399+
400+
return false
401+
}
367402
}

Tests/CodexBarTests/AmpUsageFetcherTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,31 @@ struct AmpUsageFetcherTests {
1717
#expect(!AmpUsageFetcher.shouldAttachCookie(to: URL(string: "https://ampcode.com.evil.com")))
1818
#expect(!AmpUsageFetcher.shouldAttachCookie(to: nil))
1919
}
20+
21+
@Test
22+
func detectsLoginRedirects() throws {
23+
let signIn = try #require(URL(string: "https://ampcode.com/auth/sign-in?returnTo=%2Fsettings"))
24+
#expect(AmpUsageFetcher.isLoginRedirect(signIn))
25+
26+
let sso = try #require(URL(string: "https://ampcode.com/auth/sso?returnTo=%2Fsettings"))
27+
#expect(AmpUsageFetcher.isLoginRedirect(sso))
28+
29+
let login = try #require(URL(string: "https://ampcode.com/login"))
30+
#expect(AmpUsageFetcher.isLoginRedirect(login))
31+
32+
let signin = try #require(URL(string: "https://www.ampcode.com/signin"))
33+
#expect(AmpUsageFetcher.isLoginRedirect(signin))
34+
}
35+
36+
@Test
37+
func ignoresNonLoginURLs() throws {
38+
let settings = try #require(URL(string: "https://ampcode.com/settings"))
39+
#expect(!AmpUsageFetcher.isLoginRedirect(settings))
40+
41+
let signOut = try #require(URL(string: "https://ampcode.com/auth/sign-out"))
42+
#expect(!AmpUsageFetcher.isLoginRedirect(signOut))
43+
44+
let evil = try #require(URL(string: "https://ampcode.com.evil.com/auth/sign-in"))
45+
#expect(!AmpUsageFetcher.isLoginRedirect(evil))
46+
}
2047
}

0 commit comments

Comments
 (0)