Skip to content

Commit ea4624f

Browse files
wesmclaude
andcommitted
Handle Gmail plus-address aliases in account validation
Gmail ignores +suffix in the local part, so user+tag@gmail.com is the same account as user@gmail.com. normalizeGmailAddress now strips the +suffix before comparing, preventing false TokenMismatchError for users who use tagged Gmail addresses as their account identifier. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d3c4f64 commit ea4624f

File tree

2 files changed

+14
-4
lines changed

2 files changed

+14
-4
lines changed

internal/oauth/oauth.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ func (m *Manager) resolveTokenEmail(
352352
// same Google account. This covers the common alias cases:
353353
// - exact match (case-insensitive)
354354
// - gmail.com dot-insensitive (first.last@gmail.com == firstlast@gmail.com)
355+
// - gmail.com plus-address (user+tag@gmail.com == user@gmail.com)
355356
// - googlemail.com ↔ gmail.com equivalence
356357
//
357358
// For Google Workspace domains we cannot verify aliases without an
@@ -369,9 +370,9 @@ func sameGoogleAccount(expected, canonical string) bool {
369370
}
370371

371372
// normalizeGmailAddress returns a canonical form of a gmail.com or
372-
// googlemail.com address by lowercasing, removing dots from the local
373-
// part, and mapping googlemail.com → gmail.com. Returns "" for
374-
// non-Gmail addresses.
373+
// googlemail.com address by lowercasing, stripping +suffixes and dots
374+
// from the local part, and mapping googlemail.com → gmail.com.
375+
// Returns "" for non-Gmail addresses.
375376
func normalizeGmailAddress(email string) string {
376377
at := strings.LastIndex(email, "@")
377378
if at < 0 {
@@ -384,7 +385,10 @@ func normalizeGmailAddress(email string) string {
384385
return ""
385386
}
386387

387-
// Gmail ignores dots in the local part
388+
// Gmail ignores dots and +suffixes in the local part
389+
if plus := strings.Index(local, "+"); plus >= 0 {
390+
local = local[:plus]
391+
}
388392
local = strings.ReplaceAll(local, ".", "")
389393
return local + "@gmail.com"
390394
}

internal/oauth/oauth_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,9 @@ func TestSameGoogleAccount(t *testing.T) {
787787
{"exact match", "user@gmail.com", "user@gmail.com", true},
788788
{"case insensitive", "User@Gmail.Com", "user@gmail.com", true},
789789
{"dot insensitive", "first.last@gmail.com", "firstlast@gmail.com", true},
790+
{"plus address", "user+tag@gmail.com", "user@gmail.com", true},
791+
{"plus with dots", "f.oo+bar@gmail.com", "foo@gmail.com", true},
792+
{"plus googlemail", "user+x@googlemail.com", "user@gmail.com", true},
790793
{"googlemail alias", "user@googlemail.com", "user@gmail.com", true},
791794
{"different users", "alice@gmail.com", "bob@gmail.com", false},
792795
{"different domains", "user@example.com", "user@gmail.com", false},
@@ -819,6 +822,9 @@ func TestNormalizeGmailAddress(t *testing.T) {
819822
{"first.last@gmail.com", "firstlast@gmail.com"},
820823
{"user@googlemail.com", "user@gmail.com"},
821824
{"f.i.r.s.t@googlemail.com", "first@gmail.com"},
825+
{"user+tag@gmail.com", "user@gmail.com"},
826+
{"user+@gmail.com", "user@gmail.com"},
827+
{"f.o.o+bar@googlemail.com", "foo@gmail.com"},
822828
{"user@example.com", ""},
823829
{"noatsign", ""},
824830
}

0 commit comments

Comments
 (0)