refactor: replace legacy delete-button with link-action (#38143)

Removes the legacy `delete-button` handler (`initGlobalDeleteButton`)
and migrates all remaining usages to `link-action` and `show-modal` /
`form-fetch-action`.

Two handlers are adjusted for the new request shape: webauthn key delete
reads `id` from the query, and account deletion returns `JSONError` on
validation failure.

A E2E test ist added to cover one of the use cases.

Suggested in
https://github.com/go-gitea/gitea/pull/38046#discussion_r3414936737.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
silverwind
2026-06-18 14:02:11 +02:00
committed by GitHub
parent 64f3796567
commit de83393487
29 changed files with 108 additions and 195 deletions
+12 -29
View File
@@ -242,28 +242,16 @@ func DeleteAccount(ctx *context.Context) {
return
}
ctx.Data["Title"] = ctx.Tr("settings_title")
ctx.Data["PageIsSettingsAccount"] = true
ctx.Data["Email"] = ctx.Doer.Email
if _, _, err := auth.UserSignIn(ctx, ctx.Doer.Name, ctx.FormString("password")); err != nil {
switch {
case user_model.IsErrUserNotExist(err):
loadAccountData(ctx)
ctx.RenderWithErrDeprecated(ctx.Tr("form.user_not_exist"), tplSettingsAccount, nil)
ctx.JSONError(ctx.Tr("form.user_not_exist"))
case errors.Is(err, smtp.ErrUnsupportedLoginType):
loadAccountData(ctx)
ctx.RenderWithErrDeprecated(ctx.Tr("form.unsupported_login_type"), tplSettingsAccount, nil)
ctx.JSONError(ctx.Tr("form.unsupported_login_type"))
case errors.As(err, &db.ErrUserPasswordNotSet{}):
loadAccountData(ctx)
ctx.RenderWithErrDeprecated(ctx.Tr("form.unset_password"), tplSettingsAccount, nil)
ctx.JSONError(ctx.Tr("form.unset_password"))
case errors.As(err, &db.ErrUserPasswordInvalid{}):
loadAccountData(ctx)
ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_password"), tplSettingsAccount, nil)
ctx.JSONError(ctx.Tr("form.enterred_invalid_password"))
default:
ctx.ServerError("UserSignIn", err)
}
@@ -272,32 +260,27 @@ func DeleteAccount(ctx *context.Context) {
// admin should not delete themself
if ctx.Doer.IsAdmin {
ctx.Flash.Error(ctx.Tr("form.admin_cannot_delete_self"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
ctx.JSONError(ctx.Tr("form.admin_cannot_delete_self"))
return
}
if err := user.DeleteUser(ctx, ctx.Doer, false); err != nil {
switch {
case repo_model.IsErrUserOwnRepos(err):
ctx.Flash.Error(ctx.Tr("form.still_own_repo"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
ctx.JSONError(ctx.Tr("form.still_own_repo"))
case org_model.IsErrUserHasOrgs(err):
ctx.Flash.Error(ctx.Tr("form.still_has_org"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
ctx.JSONError(ctx.Tr("form.still_has_org"))
case packages_model.IsErrUserOwnPackages(err):
ctx.Flash.Error(ctx.Tr("form.still_own_packages"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
ctx.JSONError(ctx.Tr("form.still_own_packages"))
case user_model.IsErrDeleteLastAdminUser(err):
ctx.Flash.Error(ctx.Tr("auth.last_admin"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
ctx.JSONError(ctx.Tr("auth.last_admin"))
default:
ctx.ServerError("DeleteUser", err)
}
} else {
log.Trace("Account deleted: %s", ctx.Doer.Name)
ctx.Redirect(setting.AppSubURL + "/")
return
}
ctx.JSONRedirect(setting.AppSubURL + "/")
}
func loadAccountData(ctx *context.Context) {
+14 -15
View File
@@ -247,17 +247,17 @@ func DeleteKey(ctx *context.Context) {
switch ctx.FormString("type") {
case "gpg":
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
ctx.NotFound(errors.New("gpg keys setting is not allowed to be visited"))
ctx.JSONError("gpg keys setting is not allowed to be visited")
return
}
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.FormInt64("id")); err != nil {
ctx.Flash.Error("DeleteGPGKey: " + err.Error())
} else {
ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))
ctx.JSONError("Failed to delete PGP key")
return
}
ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))
case "ssh":
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
ctx.NotFound(errors.New("ssh keys setting is not allowed to be visited"))
ctx.JSONError("ssh keys setting is not allowed to be visited")
return
}
@@ -268,24 +268,23 @@ func DeleteKey(ctx *context.Context) {
return
}
if external {
ctx.Flash.Error(ctx.Tr("settings.ssh_externally_managed"))
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
ctx.JSONError(ctx.Tr("settings.ssh_externally_managed"))
return
}
if err := asymkey_service.DeletePublicKey(ctx, ctx.Doer, keyID); err != nil {
ctx.Flash.Error("DeletePublicKey: " + err.Error())
} else {
ctx.Flash.Success(ctx.Tr("settings.ssh_key_deletion_success"))
ctx.JSONError("Failed to delete SSH key")
return
}
ctx.Flash.Success(ctx.Tr("settings.ssh_key_deletion_success"))
case "principal":
if err := asymkey_service.DeletePublicKey(ctx, ctx.Doer, ctx.FormInt64("id")); err != nil {
ctx.Flash.Error("DeletePublicKey: " + err.Error())
} else {
ctx.Flash.Success(ctx.Tr("settings.ssh_principal_deletion_success"))
ctx.JSONError("Failed to delete SSH principal key")
return
}
ctx.Flash.Success(ctx.Tr("settings.ssh_principal_deletion_success"))
default:
ctx.Flash.Warning("Function not implemented")
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
ctx.JSONError("unsupported key type")
return
}
ctx.JSONRedirect(setting.AppSubURL + "/user/settings/keys")
}
@@ -132,8 +132,7 @@ func WebauthnDelete(ctx *context.Context) {
return
}
form := web.GetForm(ctx).(*forms.WebauthnDeleteForm)
if _, err := auth.DeleteCredential(ctx, form.ID, ctx.Doer.ID); err != nil {
if _, err := auth.DeleteCredential(ctx, ctx.FormInt64("id"), ctx.Doer.ID); err != nil {
ctx.ServerError("GetWebAuthnCredentialByID", err)
return
}
+1 -1
View File
@@ -643,7 +643,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Group("/webauthn", func() {
m.Post("/request_register", web.Bind(forms.WebauthnRegistrationForm{}), security.WebAuthnRegister)
m.Post("/register", security.WebauthnRegisterPost)
m.Post("/delete", web.Bind(forms.WebauthnDeleteForm{}), security.WebauthnDelete)
m.Post("/delete", security.WebauthnDelete)
})
m.Group("/openid", func() {
m.Post("", web.Bind(forms.AddOpenIDForm{}), security.OpenIDPost)