iclouddrive: fix 2FA failing with 409 even when the code is valid
Since around mid-2026 Apple's idmsa endpoints `POST
/verify/trusteddevice/securitycode` and `POST /verify/phone/securitycode`
return HTTP 409 (instead of 2xx) even when the submitted code is accepted:
the response body carries `"securityCode": {..., "valid": true}` and the
response headers include a fresh X-Apple-Session-Token, scnt and
X-Apple-Auth-Attributes, which are only issued on successful validation.
rclone treated any 409 as failure and aborted before TrustSession, so
configuring an iclouddrive remote always failed after the 2FA step with:
validate2FACode failed: HTTP error 409 (409 ) returned body:
"{... \"securityCode\": {\"code\": \"...\", \"valid\": true} ...}"
Treat a 409 response that carries X-Apple-Session-Token as success: absorb
the session headers and continue to TrustSession. Applies to both the
trusted-device and SMS validation paths.
Fixes #9488
Closes #9534
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d183f4a43d
commit
731f2a6c29
@@ -194,6 +194,22 @@ func (s *Session) Request(ctx context.Context, opts rest.Opts, request any, resp
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// acceptedDespiteConflict returns true when Apple signals a successful code
|
||||
// validation with HTTP 409. Since ~mid-2026, idmsa returns 409 on the
|
||||
// securitycode endpoints even when the code is accepted (body carries
|
||||
// securityCode.valid=true), but it still issues X-Apple-Session-Token on
|
||||
// success. Treat token issuance as ground truth and absorb the headers so
|
||||
// TrustSession can proceed.
|
||||
func (s *Session) acceptedDespiteConflict(resp *http.Response) bool {
|
||||
if resp == nil || resp.StatusCode != 409 || resp.Header.Get("X-Apple-Session-Token") == "" {
|
||||
return false
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.extractHeaders(resp)
|
||||
s.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
// Requires2FA returns true if the session requires 2FA
|
||||
func (s *Session) Requires2FA() bool {
|
||||
if s.needs2FA {
|
||||
@@ -660,7 +676,10 @@ func (s *Session) Validate2FACode(ctx context.Context, code string) error {
|
||||
NoResponse: true,
|
||||
}
|
||||
|
||||
_, err = s.Request(ctx, opts, nil, nil)
|
||||
resp, err := s.Request(ctx, opts, nil, nil)
|
||||
if err != nil && s.acceptedDespiteConflict(resp) {
|
||||
err = nil
|
||||
}
|
||||
if err == nil {
|
||||
if err := s.TrustSession(ctx); err != nil {
|
||||
return err
|
||||
@@ -788,7 +807,10 @@ func (s *Session) ValidateSMSCode(ctx context.Context, code string, phoneID int,
|
||||
Body: body,
|
||||
NoResponse: true,
|
||||
}
|
||||
_, err = s.Request(ctx, opts, nil, nil)
|
||||
resp, err := s.Request(ctx, opts, nil, nil)
|
||||
if err != nil && s.acceptedDespiteConflict(resp) {
|
||||
err = nil
|
||||
}
|
||||
if err == nil {
|
||||
if err := s.TrustSession(ctx); err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user