Skip to content

Decrypt stored password before comparing in the 'auth' backend command - #64

Open
monperrus wants to merge 1 commit into
jmapio:masterfrom
monperrus:fix-auth-decrypt
Open

Decrypt stored password before comparing in the 'auth' backend command#64
monperrus wants to merge 1 commit into
jmapio:masterfrom
monperrus:fix-auth-decrypt

Conversation

@monperrus

Copy link
Copy Markdown

Bug

Basic-auth login (GET /session, POST /jmap) against an IMAP-backed
account fails with 401 {"type":"unauthorized"} on any deployment
that sets JMAP_SECRET_KEY (or configures OpenBao) — i.e. the
documented, recommended production credential-storage setup.

_authenticate() in bin/jmap-proxy.pl sends Basic-auth credentials
to the __accounts__ child's auth command, which does:

my $stored = $udb->selectrow_hashref(
  "SELECT password FROM iserver WHERE username = ?", {}, $email);
return ['auth', undef] unless $stored && $stored->{password} eq $password;

iserver.password is written via JMAP::CredentialStore->encrypt()
during account setup, so it's stored as enc1:base64(...) (or
vault:v1:...), never as plaintext. Comparing that directly against
the plaintext password from the request can never succeed.

The sibling verify_credentials command (a few lines below, same
file) already does this correctly:

require JMAP::CredentialStore;
my $actual = JMAP::CredentialStore->decrypt($stored->{password});
return ['verify_credentials', undef] unless $actual eq $password;

auth was just missing the decrypt step.

Fix

Bring auth in line with verify_credentials: decrypt the stored
value before comparing.

Testing

Verified against a live deployment
(ghcr.io/jmapio/jmap-proxy:latest, JMAP_SECRET_KEY set):

  • Before: curl -u 'user@example.com:correct-password' https://.../session401 {"type":"unauthorized"}
  • After: same request → 200 with a full JMAP Session object, and
    a follow-up Email/query + Email/get call against /jmap
    returned real synced mail from the account.

Related to #63 (a separate bug in the same account-setup path) but
independent — this fixes the login path specifically.

The __accounts__ 'auth' command (used for JMAP Basic-auth login on
/session and /jmap) compared the plaintext password from the request
directly against iserver.password, which JMAP::CredentialStore stores
encrypted whenever JMAP_SECRET_KEY (or OpenBao) is configured. The
comparison never matched, so Basic auth against any IMAP-backed
account failed with 401 regardless of correct credentials, on any
deployment using the documented/recommended encrypted credential
storage.

The sibling 'verify_credentials' command already does this correctly
(require JMAP::CredentialStore; decrypt before comparing) — this
brings 'auth' in line with it.

Confirmed against a live deployment (Docker image with JMAP_SECRET_KEY
set): before this change, GET /session with valid Basic-auth
credentials returned 401 {"type":"unauthorized"}; after, it returns
200 with a full JMAP Session object, and Email/query + Email/get
against the account return real synced mail.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant