Skip to content

[401] Invalid access token on GET /api/keys/salts after successful 2FA (TOTP + two-password mode) #345

Description

@kLeZ

Summary

Authentication fails for an account that has both TOTP 2FA and two-password mode (PasswordMode 2) enabled. The SRP login and the TOTP step both succeed (server returns Code:1000 and an elevated scope), but the immediately following GET /api/keys/salts is rejected with [401] Invalid access token. Auth aborts before reaching the unlock step, so no bridge password is ever generated.

Environment

  • hydroxide: 0.2.32
  • OS: Linux (x86_64)
  • Account: TOTP 2FA enabled, two-password mode (PasswordMode 2)

Steps to reproduce

  1. Use an account with both TOTP 2FA and two-password mode enabled.
  2. Run hydroxide auth <username>.
  3. Enter the login password, then the TOTP code, then the mailbox password.

Expected behavior

Login completes and a bridge password is printed.

Actual behavior

GET /api/keys/salts returns [401] Invalid access token right after the 2FA step succeeds, and auth aborts.

Debug log (anonymized)

$ hydroxide -debug auth <username>
Password:
>> POST /api/auth/info
<< POST /api/auth/info
  Code:1000  Version:4
>> POST /api/auth
<< POST /api/auth
  Code:1000
  Scope:"self parent user twofactor"
  UID:"<redacted>"
  AccessToken:"<redacted>"
  RefreshToken:"<redacted>"
  PasswordMode:2
  TwoFactor:{Enabled:1, U2F:<nil>, TOTP:1}
  ExpiresIn:1800  TokenType:"Bearer"
2FA TOTP code: ******
>> POST /api/auth/2fa
  {"TwoFactorCode":"******"}
<< POST /api/auth/2fa
  Code:1000
  Scope:"full self payments keys parent user loggedin nondelinquent mail vpn calendar drive docs pass verified settings wallet lumo meet"
Mailbox password:
>> GET /api/keys/salts
<< GET /api/keys/salts
  Code:401
request failed: GET https://mail.proton.me/api/keys/salts: [401] Invalid access token
[401] Invalid access token

Analysis

Observations from the debug log:

  • POST /api/auth succeeds with Scope:"self parent user twofactor" and returns an access/refresh token pair.
  • POST /api/auth/2fa succeeds (Code:1000) and the response carries the elevated scope "full self payments keys ... mail ...". The response struct hydroxide parses for this call (struct { resp; Scope string }) only contains the scope, no refreshed token.
  • The subsequent GET /api/keys/salts, sent with the access token obtained before the 2FA step, is rejected with 401.
  • Token expiry is not the cause: ExpiresIn was 1800s and the salts call happened within a few seconds of login.

This suggests the API invalidates or rotates the pre-2FA access token once the second factor is completed, and expects the client to obtain an updated token before issuing further requests (e.g. via POST /api/auth/refresh with the refresh token, or by reading an updated token from the 2FA response). hydroxide keeps using the pre-2FA token, whose scope was "...twofactor", and the server no longer accepts it.

I observed this with both TOTP 2FA and two-password mode enabled together; I have not tested whether either alone reproduces it.

Possibly related

#341 (POST /api/auth/refresh -> [10013] Invalid refresh token)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions