fix(mailer): reject incoming email tokens of users who cannot sign in (#39833)

Incoming email reply tokens are embedded in every notification email and
stay valid for a year. `DecodeToken` verifies them with `user.Rands`,
which is not rotated when an admin disables sign-in or the account is
deactivated, and `ReplyHandler` only checks repo read permission. A
suspended user could therefore keep posting comments, review comments
and attachments on any public repo by replying to old notifications,
while the web, API, git and package paths all refuse them. `DecodeToken`
now rejects tokens of users who are inactive or prohibited from signing
in, which covers every incoming mail handler.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Basavaraj S mandwxiaoguang authored and GitHub committed 2026-10-18 10:20:00 +00:00
1 parent 52b18b4903
commit 18d0ee174d
3 files changed
+63 -3

No files matched your search

+1 -1
View File
@@ -98,7 +98,7 @@ func DecodeToken(ctx context.Context, token string) (HandlerType, *user_model.Us
return UnknownHandlerType, nil, nil, err
}
user, err := user_model.GetUserByID(ctx, userID)
user, err := user_model.GetValidDoerUserByID(ctx, userID)
if err != nil {
return UnknownHandlerType, nil, nil, err
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package token
import (
"testing"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
_ "gitea.dev/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
unittest.MainTest(m)
}
func TestDecodeTokenRejectsUserWhoCannotSignIn(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
for id, expectedErr := range map[int64]string{2: "", 3: "not a doer", 9: "inactive", 37: "prohibited"} {
token, err := CreateToken(ReplyHandlerType, unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: id}), []byte{1})
require.NoError(t, err)
_, _, _, err = DecodeToken(t.Context(), token)
if expectedErr == "" {
assert.NoError(t, err)
} else {
assert.ErrorContains(t, err, expectedErr)
}
}
}