fix: require password login for creation of new token (#9070)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9070
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2025-08-30 13:12:54 +02:00
commit c064ce4ad0
3 changed files with 87 additions and 2 deletions

View file

@ -414,8 +414,11 @@ func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
return
}
if !ctx.IsBasicAuth {
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required")
// Require basic authorization method to be used and that basic
// authorization used password login to verify the user.
if passwordLogin, ok := ctx.Data["IsPasswordLogin"].(bool); !ok || !passwordLogin {
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth method not allowed")
return
}
}

View file

@ -151,6 +151,7 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
log.Trace("Basic Authorization: Logged in user %-v", u)
store.GetData()["IsPasswordLogin"] = true
return u, nil
}

View file

@ -4,8 +4,10 @@
package integration
import (
"encoding/base64"
"fmt"
"net/http"
"net/url"
"testing"
auth_model "forgejo.org/models/auth"
@ -13,9 +15,11 @@ import (
user_model "forgejo.org/models/user"
"forgejo.org/modules/log"
api "forgejo.org/modules/structs"
"forgejo.org/modules/test"
"forgejo.org/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestAPICreateAndDeleteToken tests that token that was just created can be deleted
@ -580,3 +584,80 @@ func deleteAPIAccessToken(t *testing.T, accessToken api.AccessToken, user *user_
unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: accessToken.ID})
}
func TestAPITokenCreation(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user4")
t.Run("Via API token", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
req := NewRequestWithJSON(t, "POST", "/api/v1/users/user4/tokens", map[string]any{
"name": "new-new-token",
"scopes": []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteUser},
})
req.Request.Header.Set("Authorization", "basic "+base64.StdEncoding.EncodeToString([]byte("user4:"+token)))
resp := MakeRequest(t, req, http.StatusUnauthorized)
respMsg := map[string]any{}
DecodeJSON(t, resp, &respMsg)
assert.EqualValues(t, "auth method not allowed", respMsg["message"])
})
t.Run("Via OAuth2", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
"_csrf": GetCSRF(t, session, "/login/oauth/authorize?client_id=ce5a1322-42a7-11ed-b878-0242ac120002&redirect_uri=b&response_type=code&code_challenge_method=plain&code_challenge=CODE&state=thestate"),
"client_id": "ce5a1322-42a7-11ed-b878-0242ac120002",
"redirect_uri": "b",
"state": "thestate",
"granted": "true",
})
resp := session.MakeRequest(t, req, http.StatusSeeOther)
u, err := url.Parse(test.RedirectURL(resp))
require.NoError(t, err)
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
"client_id": "ce5a1322-42a7-11ed-b878-0242ac120002",
"code": u.Query().Get("code"),
"code_verifier": "CODE",
"grant_type": "authorization_code",
"redirect_uri": "b",
})
resp = MakeRequest(t, req, http.StatusOK)
var respBody map[string]any
DecodeJSON(t, resp, &respBody)
req = NewRequestWithJSON(t, "POST", "/api/v1/users/user4/tokens", map[string]any{
"name": "new-new-token",
"scopes": []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteUser},
})
req.Request.Header.Set("Authorization", "basic "+base64.StdEncoding.EncodeToString([]byte("user4:"+respBody["access_token"].(string))))
resp = MakeRequest(t, req, http.StatusUnauthorized)
respMsg := map[string]any{}
DecodeJSON(t, resp, &respMsg)
assert.EqualValues(t, "auth method not allowed", respMsg["message"])
})
t.Run("Via password", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithJSON(t, "POST", "/api/v1/users/user4/tokens", map[string]any{
"name": "new-new-token",
"scopes": []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteUser},
})
req.Request.Header.Set("Authorization", "basic "+base64.StdEncoding.EncodeToString([]byte("user4:"+userPassword)))
MakeRequest(t, req, http.StatusCreated)
})
}