feat(auth): add ability to regenerate access tokens (#6963)

- Add the ability to regenerate existing access tokens in the UI. This preserves the ID of the access token, but generates a new salt and token contents.
- Integration test added.
- Unit test added.
- Resolves #6880

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6963
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Dmitrii Sharshakov <d3dx12.xx@gmail.com>
Co-committed-by: Dmitrii Sharshakov <d3dx12.xx@gmail.com>
This commit is contained in:
Dmitrii Sharshakov 2025-03-08 10:42:36 +00:00 committed by Gusted
parent 9dea54a9d6
commit 30982b9e7b
8 changed files with 176 additions and 7 deletions

View file

@ -30,6 +30,7 @@ import (
"code.gitea.io/gitea/services/mailer"
"code.gitea.io/gitea/tests"
"github.com/PuerkitoBio/goquery"
"github.com/pquerna/otp/totp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -247,6 +248,69 @@ func testExportUserGPGKeys(t *testing.T, user, expected string) {
assert.Equal(t, expected, resp.Body.String())
}
func TestAccessTokenRegenerate(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1")
prevLatestTokenName, prevLatestTokenID := findLatestTokenID(t, session)
createApplicationSettingsToken(t, session, "TestAccessToken", auth_model.AccessTokenScopeWriteUser)
oldToken := assertAccessToken(t, session)
oldTokenName, oldTokenID := findLatestTokenID(t, session)
assert.Equal(t, "TestAccessToken", oldTokenName)
req := NewRequestWithValues(t, "POST", "/user/settings/applications/regenerate", map[string]string{
"_csrf": GetCSRF(t, session, "/user/settings/applications"),
"id": strconv.Itoa(oldTokenID),
})
session.MakeRequest(t, req, http.StatusOK)
newToken := assertAccessToken(t, session)
newTokenName, newTokenID := findLatestTokenID(t, session)
assert.NotEqual(t, oldToken, newToken)
assert.Equal(t, oldTokenID, newTokenID)
assert.Equal(t, "TestAccessToken", newTokenName)
req = NewRequestWithValues(t, "POST", "/user/settings/applications/delete", map[string]string{
"_csrf": GetCSRF(t, session, "/user/settings/applications"),
"id": strconv.Itoa(newTokenID),
})
session.MakeRequest(t, req, http.StatusOK)
latestTokenName, latestTokenID := findLatestTokenID(t, session)
assert.Less(t, latestTokenID, oldTokenID)
assert.Equal(t, latestTokenID, prevLatestTokenID)
assert.Equal(t, latestTokenName, prevLatestTokenName)
assert.NotEqual(t, "TestAccessToken", latestTokenName)
}
func findLatestTokenID(t *testing.T, session *TestSession) (string, int) {
req := NewRequest(t, "GET", "/user/settings/applications")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
latestTokenName := ""
latestTokenID := 0
htmlDoc.Find(".delete-button").Each(func(i int, s *goquery.Selection) {
tokenID, exists := s.Attr("data-id")
if !exists || tokenID == "" {
return
}
id, err := strconv.Atoi(tokenID)
require.NoError(t, err)
if id > latestTokenID {
latestTokenName = s.Parent().Parent().Find(".flex-item-title").Text()
latestTokenID = id
}
})
return latestTokenName, latestTokenID
}
func TestGetUserRss(t *testing.T) {
defer tests.PrepareTestEnv(t)()