From 6ac89575831077187b75e544bef1acfc6f11ee6c Mon Sep 17 00:00:00 2001 From: disqualifier Date: Sun, 28 Jun 2026 18:45:25 -0400 Subject: [PATCH] fix: pass XOAUTH2 token as str, not bytes (was corrupting the Bearer value) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit aioimaplib's mail.xoauth2(user, token) builds the SASL string by f-string interpolating the token, so a bytes token injects the b'...' repr into auth=Bearer and breaks every XOAUTH2 login. dropped the .encode() (token is already str via _resolve_token/_as_str). corrected the inline comment and the CLAUDE.md note that both wrongly claimed bytes was required — that false note propagated the bug through prior review passes. Signed-off-by: disqualifier --- src/aiomail/auth.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/aiomail/auth.py b/src/aiomail/auth.py index 7601966..f968bab 100644 --- a/src/aiomail/auth.py +++ b/src/aiomail/auth.py @@ -85,12 +85,14 @@ class OAuth2Auth: async def authenticate(self, mail) -> None: token = await self._resolve_token() - # aioimaplib exposes mail.xoauth2(user, token: bytes) — note the token must - # be bytes, not str. older/other clients that lack it but expose a generic - # authenticate() are driven via the SASL string from _sasl_xoauth2. + # aioimaplib's mail.xoauth2(user, token) builds the SASL string by f-string + # interpolating the token, so token MUST be str — passing bytes interpolates + # the b'...' repr and corrupts the Bearer value. _resolve_token already + # returns str (via _as_str). clients lacking .xoauth2 are driven via the + # SASL callback from _sasl_xoauth2. xoauth2 = getattr(mail, "xoauth2", None) if xoauth2 is not None: - result, data = await xoauth2(self.user, token.encode()) + result, data = await xoauth2(self.user, token) elif hasattr(mail, "authenticate"): result, data = await mail.authenticate( "XOAUTH2", lambda _: _sasl_xoauth2(self.user, token)