fix(api): allow bots with pending password changes (#39551)

Allow bot accounts to use the API when a legacy password-change flag is
set, since bots cannot complete the interactive password-change flow.
Preserve password-change enforcement for human accounts and restrictions
for inactive or prohibited accounts.

Fixes: https://github.com/go-gitea/gitea/issues/39542
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
authored and GitHub committed 2026-10-03 17:22:07 +00:00
1 parent 516a4883fa
commit cca466caae
11 files changed
+129 -100

No files matched your search

+18 -34
View File
@@ -76,6 +76,7 @@ import (
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unit"
user_model "gitea.dev/models/user"
"gitea.dev/modules/httplib"
"gitea.dev/modules/log"
"gitea.dev/modules/setting"
api "gitea.dev/modules/structs"
@@ -935,31 +936,22 @@ func apiAuth(authMethod auth.Method) func(*context.APIContext) {
}
}
// verifyAuthWithOptions checks authentication according to options
func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIContext) {
// verifyAuthWithOptionsAPI checks authentication according to options
func verifyAuthWithOptionsAPI(options *common.VerifyOptions) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
// Check prohibit login users.
if ctx.IsSigned {
if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "This account is not activated.",
})
check := common.CheckSignedInUser(ctx.Doer, nil)
if check.NeedActivateAccount {
ctx.JSON(http.StatusForbidden, map[string]string{"message": "This account is not activated."})
return
}
if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
} else if check.LoginIsProhibited {
log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "This account is prohibited from signing in, please contact your site administrator.",
})
ctx.JSON(http.StatusForbidden, map[string]string{"message": "This account is prohibited from signing in, please contact your site administrator."})
return
}
if ctx.Doer.MustChangePassword {
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
})
} else if check.NeedChangePassword {
msg := "You must change your password. Change it at: " + httplib.MakeAbsoluteURL(ctx, setting.AppSubURL+"/user/settings/change_password")
ctx.JSON(http.StatusForbidden, map[string]string{"message": msg})
return
}
}
@@ -970,20 +962,12 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
return
}
if options.SignInRequired {
if !ctx.IsSigned {
// Restrict API calls with error message.
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "Only signed in user is allowed to call APIs.",
})
return
} else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "This account is not activated.",
})
return
}
if options.SignInRequired && !ctx.IsSigned {
// Restrict API calls with error message.
ctx.JSON(http.StatusForbidden, map[string]string{
"message": "Only signed in user is allowed to call APIs.",
})
return
}
if options.AdminRequired {
@@ -1035,7 +1019,7 @@ func Routes() *web.Router {
// Get user from session if logged in.
m.AfterRouting(apiAuth(buildAuthGroup()))
m.AfterRouting(verifyAuthWithOptions(&common.VerifyOptions{
m.AfterRouting(verifyAuthWithOptionsAPI(&common.VerifyOptions{
SignInRequired: setting.Service.RequireSignInViewStrict,
}))