Summary
Proton Drive macOS 2.10.3 fails to sign in with "Possible decoding error" after successful SRP + TOTP authentication. The root cause is a type mismatch in UserFlags decoding: the /users API returns Flags values as integers (0/1), but UserFlags declares its fields as Bool?, and Swift's Codable does not auto-coerce Int to Bool.
The server response is actually successful (Code: 1000) with a complete User object — the client just can't parse it.
Environment
- Proton Drive macOS 2.10.3 (build 11543)
- protoncore_ios 33.2.0 (also unfixed on
develop HEAD and tag 33.5.1)
- macOS 26.3.1 and 26.4
- Account with TOTP 2FA enabled
- Proton Mail and ProtonVPN desktop apps are not affected (likely use a different deserialization path)
Steps to reproduce
- Install Proton Drive 2.10.3 on macOS
- Sign in with a Proton account that has TOTP 2FA enabled
- Enter credentials and TOTP code
- App displays "Possible decoding error"
Root cause
After TOTP succeeds, the login flow calls GET /users. The server returns a successful response (Code: 1000) containing:
"Flags": {
"has-a-byoe-address": 0,
"has-temporary-password": 0,
"sso": 0,
...
}
These values are integers, but UserFlags in libraries/DataModel/Sources/User.swift declares them as Bool?:
public struct UserFlags: Codable, Equatable {
public let hasBYOEAddress: Bool?
public let hasTemporaryPassword: Bool?
public let sso: Bool?
// ...
}
Swift's JSONDecoder throws DecodingError.typeMismatch when it encounters 0 where it expects Bool. This causes the entire User decode to fail, which triggers the responseBodyIsNotADecodableObject error path in Session.swift:66, producing the misleading "Possible decoding error" message.
The error chain:
GET /users → 200, Code 1000 (success!)
→ JSONDecoder fails on UserFlags.sso: expected Bool, got Int
→ SessionResponseError.responseBodyIsNotADecodableObject
→ "Possible decoding error. See body in next line for more info."
→ LoginError.generic → shown in UI
Proposed fix
Add a custom init(from:) to UserFlags that accepts both Bool and Int:
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
hasBYOEAddress = Self.decodeBool(from: container, key: .hasBYOEAddress)
hasTemporaryPassword = Self.decodeBool(from: container, key: .hasTemporaryPassword)
sso = Self.decodeBool(from: container, key: .sso)
}
private static func decodeBool(from container: KeyedDecodingContainer<CodingKeys>, key: CodingKeys) -> Bool? {
if let value = try? container.decodeIfPresent(Bool.self, forKey: key) {
return value
}
if let intValue = try? container.decodeIfPresent(Int.self, forKey: key) {
return intValue != 0
}
return nil
}
This is backward-compatible — existing boolean responses decode as before, and integer responses are coerced to Bool.
I have a patch ready if a PR would be preferred.
Additional notes
- The server may also need to be fixed to return proper booleans, but the client should be resilient to both types regardless.
- The "Possible decoding error" message in
Session.swift:66 is misleading for users and support — it would be helpful to surface the actual DecodingError details in a future improvement.
- Verified that
develop HEAD and tag 33.5.1 are both still affected.
Summary
Proton Drive macOS 2.10.3 fails to sign in with "Possible decoding error" after successful SRP + TOTP authentication. The root cause is a type mismatch in
UserFlagsdecoding: the/usersAPI returnsFlagsvalues as integers (0/1), butUserFlagsdeclares its fields asBool?, and Swift'sCodabledoes not auto-coerceInttoBool.The server response is actually successful (
Code: 1000) with a completeUserobject — the client just can't parse it.Environment
developHEAD and tag 33.5.1)Steps to reproduce
Root cause
After TOTP succeeds, the login flow calls
GET /users. The server returns a successful response (Code: 1000) containing:These values are integers, but
UserFlagsinlibraries/DataModel/Sources/User.swiftdeclares them asBool?:Swift's
JSONDecoderthrowsDecodingError.typeMismatchwhen it encounters0where it expectsBool. This causes the entireUserdecode to fail, which triggers theresponseBodyIsNotADecodableObjecterror path inSession.swift:66, producing the misleading "Possible decoding error" message.The error chain:
Proposed fix
Add a custom
init(from:)toUserFlagsthat accepts bothBoolandInt:This is backward-compatible — existing boolean responses decode as before, and integer responses are coerced to
Bool.I have a patch ready if a PR would be preferred.
Additional notes
Session.swift:66is misleading for users and support — it would be helpful to surface the actualDecodingErrordetails in a future improvement.developHEAD and tag33.5.1are both still affected.