diff --git a/docs/releases.md b/docs/releases.md index 0e93b677..03b434ce 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1227,6 +1227,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release * Support encoding any header as RFC 2047 ([#737](https://github.com/binwiederhier/ntfy/issues/737), thanks to [@cfouche3005](https://github.com/cfouche3005) for reporting) * Do not forward poll requests for UnifiedPush messages (no ticket, thanks to NoName for reporting) * Fix `ntfy pub %` segfaulting ([#760](https://github.com/binwiederhier/ntfy/issues/760), thanks to [@clesmian](https://github.com/clesmian) for reporting) +* Newly created access tokens are now lowercase only to fully support `+@` email syntax ([#773](https://github.com/binwiederhier/ntfy/issues/773), thanks to gingervitiz for reporting) **Maintenance:** diff --git a/user/manager.go b/user/manager.go index 00407ab3..3307134a 100644 --- a/user/manager.go +++ b/user/manager.go @@ -508,7 +508,7 @@ func (a *Manager) AuthenticateToken(token string) (*User, error) { // after a fixed duration unless ChangeToken is called. This function also prunes tokens for the // given user, if there are too many of them. func (a *Manager) CreateToken(userID, label string, expires time.Time, origin netip.Addr) (*Token, error) { - token := util.RandomStringPrefix(tokenPrefix, tokenLength) + token := util.RandomLowerStringPrefix(tokenPrefix, tokenLength) // Lowercase only to support "+@" email addresses tx, err := a.db.Begin() if err != nil { return nil, err diff --git a/user/manager_test.go b/user/manager_test.go index 5e01f497..85e3c428 100644 --- a/user/manager_test.go +++ b/user/manager_test.go @@ -183,6 +183,19 @@ func TestManager_MarkUserRemoved_RemoveDeletedUsers(t *testing.T) { require.Equal(t, ErrUserNotFound, err) } +func TestManager_CreateToken_Only_Lower(t *testing.T) { + a := newTestManager(t, PermissionDenyAll) + + // Create user, add reservations and token + require.Nil(t, a.AddUser("user", "pass", RoleAdmin)) + u, err := a.User("user") + require.Nil(t, err) + + token, err := a.CreateToken(u.ID, "", time.Now().Add(time.Hour), netip.IPv4Unspecified()) + require.Nil(t, err) + require.Equal(t, token.Value, strings.ToLower(token.Value)) +} + func TestManager_UserManagement(t *testing.T) { a := newTestManager(t, PermissionDenyAll) require.Nil(t, a.AddUser("phil", "phil", RoleAdmin)) diff --git a/util/util.go b/util/util.go index 84177d9f..4a63e22f 100644 --- a/util/util.go +++ b/util/util.go @@ -23,7 +23,8 @@ import ( ) const ( - randomStringCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + randomStringCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + randomStringLowerCaseCharset = "abcdefghijklmnopqrstuvwxyz0123456789" ) var ( @@ -112,11 +113,20 @@ func RandomString(length int) string { // RandomStringPrefix returns a random string with a given length, with a prefix func RandomStringPrefix(prefix string, length int) string { + return randomStringPrefixWithCharset(prefix, length, randomStringCharset) +} + +// RandomLowerStringPrefix returns a random lowercase-only string with a given length, with a prefix +func RandomLowerStringPrefix(prefix string, length int) string { + return randomStringPrefixWithCharset(prefix, length, randomStringLowerCaseCharset) +} + +func randomStringPrefixWithCharset(prefix string, length int, charset string) string { randomMutex.Lock() // Who would have thought that random.Intn() is not thread-safe?! defer randomMutex.Unlock() b := make([]byte, length-len(prefix)) for i := range b { - b[i] = randomStringCharset[random.Intn(len(randomStringCharset))] + b[i] = charset[random.Intn(len(charset))] } return prefix + string(b) }