SMS 2FA fails with HTTP 412 when the trusted phone number ID isn't 1
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 48/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- swift
- Ambito
- authentication, mobile-dev
Direzione di ricerca
Start with Sources/XKit/GrandSlam/Operations/GrandSlamTwoFactorAuthenticateOperation.swift and the SMS request types in Sources/XKit/GrandSlam/Requests/Concrete/GrandSlamTrustedDeviceRequest.swift and GrandSlamValidateRequest.swift. Trace how HTTP 412 responses and trusted phone number data are handled, then check existing tests for these requests. Done means SMS login works when the trusted phone number ID is not 1, including code validation; the issue reports a successful manual test but names no automated test.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Description
SMS 2FA login fails on accounts whose trusted phone number doesn't have ID 1. performSMSAuth hardcodes phoneNumberID = 1, so Apple rejects the request and no SMS is sent:
Logging in...
Error: PUT https://gsa.apple.com/auth/verify/phone failed: HTTP 412. Details:
The response lists the valid numbers (personal data removed):
{
"success": 0,
"trustedPhoneNumbers": [
{ "nonFTEU": true, "pushMode": "sms", "id": 2 }
],
"mode": "sms"
}
Steps to reproduce
- Use an Apple ID with no Apple devices, whose only trusted number has an ID other than 1.
- Run
xtool auth loginand choose1: Password. - Enter the Apple ID and password.
Environment
- Host OS: Linux, Ubuntu 26.04.1 LTS, x86_64
- xtool: commit
bec5aa106(includes #416), debug build - Swift version: 6.4.1
- iOS device version: n/a (no iOS device on the account)
Proposed fix
On a 412, read trustedPhoneNumbers from the response and retry with the first entry's id and nonFTEU. Use the same values when validating the code:
} catch let error as HTTPResponseError where error.status.code == 412 {
// the ID is not always 1; Apple lists the valid ones in the 412 response
guard let trusted = error.body
.flatMap({ try? JSONDecoder().decode(PhoneVerificationResponse.self, from: Data($0.utf8)) })?
.trustedPhoneNumbers.first
else { throw error }
phoneNumber = trusted
try await sendSMS(to: phoneNumber)
}
The full patch is below.
Full patch
diff --git a/Sources/XKit/GrandSlam/Operations/GrandSlamTwoFactorAuthenticateOperation.swift b/Sources/XKit/GrandSlam/Operations/GrandSlamTwoFactorAuthenticateOperation.swift
index 5a41e02..a0f1dde 100644
--- a/Sources/XKit/GrandSlam/Operations/GrandSlamTwoFactorAuthenticateOperation.swift
+++ b/Sources/XKit/GrandSlam/Operations/GrandSlamTwoFactorAuthenticateOperation.swift
@@ -25,11 +25,37 @@ struct GrandSlamTwoFactorAuthenticateOperation {
let loginData: GrandSlamLoginData
unowned let delegate: TwoFactorAuthDelegate
+ private struct TrustedPhoneNumber: Decodable {
+ let id: Int
+ var nonFTEU: Bool?
+ }
+
+ private struct PhoneVerificationResponse: Decodable {
+ let trustedPhoneNumbers: [TrustedPhoneNumber]
+ }
+
+ private func sendSMS(to phoneNumber: TrustedPhoneNumber) async throws {
+ try await client.send(GrandSlamSMSAuthRequest(
+ loginData: loginData,
+ phoneNumberID: phoneNumber.id,
+ nonFTEU: phoneNumber.nonFTEU
+ ))
+ }
+
private func performSMSAuth() async throws {
- // TODO: parse phone number list from o=complete req?
- let phoneNumberID = 1
+ var phoneNumber = TrustedPhoneNumber(id: 1)
do {
- try await client.send(GrandSlamSMSAuthRequest(loginData: loginData, phoneNumberID: phoneNumberID))
+ do {
+ try await sendSMS(to: phoneNumber)
+ } catch let error as HTTPResponseError where error.status.code == 412 {
+ // the ID is not always 1; Apple lists the valid ones in the 412 response
+ guard let trusted = error.body
+ .flatMap({ try? JSONDecoder().decode(PhoneVerificationResponse.self, from: Data($0.utf8)) })?
+ .trustedPhoneNumbers.first
+ else { throw error }
+ phoneNumber = trusted
+ try await sendSMS(to: phoneNumber)
+ }
} catch let error as HTTPResponseError where error.status.code == 403 {
d.out.print("""
warning: Could not send verification code via SMS. Falling back to trusted device authentication...
@@ -37,15 +63,16 @@ struct GrandSlamTwoFactorAuthenticateOperation {
try await performTrustedDeviceAuth()
return
}
- try await validateCode(phoneNumberID: phoneNumberID)
+ try await validateCode(phoneNumber: phoneNumber)
}
- private func validateCode(phoneNumberID: Int? = nil) async throws {
+ private func validateCode(phoneNumber: TrustedPhoneNumber? = nil) async throws {
guard let code = await self.delegate.fetchCode() else { throw Error.userCancelled }
- let request: any GrandSlamRequest = if let phoneNumberID {
+ let request: any GrandSlamRequest = if let phoneNumber {
GrandSlamValidateSMSRequest(
loginData: loginData,
- phoneNumberID: phoneNumberID,
+ phoneNumberID: phoneNumber.id,
+ nonFTEU: phoneNumber.nonFTEU,
verificationCode: code
)
} else {
diff --git a/Sources/XKit/GrandSlam/Requests/Concrete/GrandSlamTrustedDeviceRequest.swift b/Sources/XKit/GrandSlam/Requests/Concrete/GrandSlamTrustedDeviceRequest.swift
index 628ba9f..545a7f4 100644
--- a/Sources/XKit/GrandSlam/Requests/Concrete/GrandSlamTrustedDeviceRequest.swift
+++ b/Sources/XKit/GrandSlam/Requests/Concrete/GrandSlamTrustedDeviceRequest.swift
@@ -39,6 +39,7 @@ struct GrandSlamSMSAuthRequest: GrandSlamTwoFactorRequest {
let loginData: GrandSlamLoginData
let phoneNumberID: Int
+ var nonFTEU: Bool?
var extraHeaders: [String: String] {
[
@@ -47,9 +48,15 @@ struct GrandSlamSMSAuthRequest: GrandSlamTwoFactorRequest {
]
}
+ private var phoneNumber: [String: Any] {
+ var phoneNumber: [String: Any] = ["id": phoneNumberID]
+ if let nonFTEU { phoneNumber["nonFTEU"] = nonFTEU }
+ return phoneNumber
+ }
+
func content(anisetteData: AnisetteData) -> GrandSlamRequestContent {
.put([
- "phoneNumber": ["id": phoneNumberID],
+ "phoneNumber": phoneNumber,
"mode": "sms",
], type: .json)
}
diff --git a/Sources/XKit/GrandSlam/Requests/Concrete/GrandSlamValidateRequest.swift b/Sources/XKit/GrandSlam/Requests/Concrete/GrandSlamValidateRequest.swift
index b93c614..b9f1e13 100644
--- a/Sources/XKit/GrandSlam/Requests/Concrete/GrandSlamValidateRequest.swift
+++ b/Sources/XKit/GrandSlam/Requests/Concrete/GrandSlamValidateRequest.swift
@@ -34,6 +34,7 @@ struct GrandSlamValidateSMSRequest: GrandSlamTwoFactorRequest {
let loginData: GrandSlamLoginData
let phoneNumberID: Int
+ var nonFTEU: Bool?
let verificationCode: String
var extraHeaders: [String: String] {
@@ -43,10 +44,16 @@ struct GrandSlamValidateSMSRequest: GrandSlamTwoFactorRequest {
]
}
+ private var phoneNumber: [String: Any] {
+ var phoneNumber: [String: Any] = ["id": phoneNumberID]
+ if let nonFTEU { phoneNumber["nonFTEU"] = nonFTEU }
+ return phoneNumber
+ }
+
func content(anisetteData: AnisetteData) -> GrandSlamRequestContent {
.post([
"securityCode": ["code": verificationCode],
- "phoneNumber": ["id": phoneNumberID],
+ "phoneNumber": phoneNumber,
"mode": "sms",
], type: .json)
}
Testing
With this patch, the SMS arrived and login completed on the affected account.
Not tested:
- Whether
nonFTEUis actually required - Accounts with more than one trusted number (the patch always picks the first)
The diagnosis and patch were written with Claude Code. I tested the login on my account.
- Lingua principale
- Swift
- Stelle
- 5.5k
- Fork
- 163
- Merge medio
- 1g 12h
- PR unite (30g)
- 32
Preparare l'ambiente
- Include un Dockerfile o un file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di xtool-org/xtool
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 63/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 58/100
I maintainer di solito rispondono entro 1 giorno
-
Release build crashes the compiler on Linux since #389 (RCodesign)Forse già presa @c4software l’ha presa 2 giorni fa. Aperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 52/100
xtool-org/xtool#431 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 50/100
xtool-org/xtool#414 · 1 commento · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 4/5 3-5 giorni Idoneità per principianti 46/100
xtool-org/xtool#407 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di xtool-org/xtool
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
-
Bug: on hover a tab's name in Cra stops ~16 pt short of Arc's, with 24 pt of air before the ✕Apertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
VictorKildahl/cra#243 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
OneBusAway/vehicle-positions#260 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
eliasstravik/herdr-projects#789 · 1 commento ·
-
Linux: Remember Password fails silently when there is no default keyringForse già presa @datlechin l’ha presa oggi. Apertabug platform-linux
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
TableProApp/TablePro#4098 ·
I maintainer di solito rispondono entro 1 giorno