Skip to content

fix: UserFlags decoding fails when API returns integers instead of booleans — breaks login on Proton Drive macOS #12

@amrith

Description

@amrith

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

  1. Install Proton Drive 2.10.3 on macOS
  2. Sign in with a Proton account that has TOTP 2FA enabled
  3. Enter credentials and TOTP code
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions