Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): delete the authenticated user #20410

Closed
wants to merge 13 commits into from
4 changes: 3 additions & 1 deletion routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,9 @@ func Routes(ctx gocontext.Context) *web.Route {
}, reqToken())

m.Group("/user", func() {
m.Get("", user.GetAuthenticatedUser)
m.Combo("").
Get(user.GetAuthenticatedUser).
Delete(reqBasicOrRevProxyAuth(), user.DeleteAuthenticatedUser)
m.Group("/settings", func() {
m.Get("", user.GetUserSettings)
m.Patch("", bind(api.UserSettingsOptions{}), user.UpdateUserSettings)
Expand Down
47 changes: 47 additions & 0 deletions routers/api/v1/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
package user

import (
"fmt"
"net/http"

"code.gitea.io/gitea/models"
activities_model "code.gitea.io/gitea/models/activities"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/api/v1/utils"
user_service "code.gitea.io/gitea/services/user"
)

// Search search users
Expand Down Expand Up @@ -146,3 +150,46 @@ func GetUserHeatmapData(ctx *context.APIContext) {
}
ctx.JSON(http.StatusOK, heatmap)
}

// DeleteAuthenticatedUser deletes the current user
func DeleteAuthenticatedUser(ctx *context.APIContext) {
// swagger:operation DELETE /user user userDeleteCurrent
// ---
// summary: Delete the authenticated user
// produces:
// - application/json
6543 marked this conversation as resolved.
Show resolved Hide resolved
// parameters:
// - name: purge
// in: query
// description: Purge user, all their repositories, organizations and comments
// type: boolean
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"

// Extract out the username as it's unavailable after deleting the user.
username := ctx.Doer.Name

if ctx.Doer.IsOrganization() {
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("%s is an organization not a user", username))
return
}

if err := user_service.DeleteUser(ctx, ctx.Doer, ctx.FormBool("purge")); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should ensure only site administrator could do it.

if models.IsErrUserOwnRepos(err) ||
models.IsErrUserHasOrgs(err) ||
models.IsErrUserOwnPackages(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "DeleteAuthenticatedUser", err)
}
return
}
log.Trace("Account deleted: %s", username)

ctx.Status(http.StatusNoContent)
}
29 changes: 29 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11760,6 +11760,35 @@
"$ref": "#/responses/User"
}
}
},
"delete": {
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Delete the authenticated user",
"operationId": "userDeleteCurrent",
"parameters": [
{
"type": "boolean",
"description": "Purge user, all their repositories, organizations and comments",
"name": "purge",
"in": "query"
}
],
"responses": {
"204": {
"$ref": "#/responses/empty"
},
"403": {
"$ref": "#/responses/forbidden"
},
"422": {
"$ref": "#/responses/validationError"
}
}
}
},
"/user/applications/oauth2": {
Expand Down
52 changes: 52 additions & 0 deletions tests/integration/api_user_delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integration

import (
"net/http"
"testing"

repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/tests"
)

func TestAPIDeleteUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()

// 1 -> Admin
// 8 -> Normal user
for _, userID := range []int64{1, 8} {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
t.Logf("Testing username %s", user.Name)

req := NewRequest(t, "DELETE", "/api/v1/user")
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNoContent)

assertUserDeleted(t, userID)
unittest.CheckConsistencyFor(t, &user_model.User{})
}
}

func TestAPIPurgeUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})

// Cannot delete the user as it still has ownership of repositories
req := NewRequest(t, "DELETE", "/api/v1/user")
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusUnprocessableEntity)

unittest.CheckConsistencyFor(t, &user_model.User{ID: 5})

req = NewRequest(t, "DELETE", "/api/v1/user?purge=true")
req = AddBasicAuthHeader(req, user.Name)
MakeRequest(t, req, http.StatusNoContent)

assertUserDeleted(t, 5)
unittest.CheckConsistencyFor(t, &user_model.User{}, &repo_model.Repository{})
}