API: implement user account deletion

This commit is contained in:
Adnan Maolood 2022-11-01 11:32:57 -04:00 committed by Drew DeVault
parent a26c7a828d
commit 0fb8803a60
5 changed files with 83 additions and 3 deletions

53
api/account/middleware.go Normal file
View File

@ -0,0 +1,53 @@
package account
import (
"context"
"database/sql"
"log"
"net/http"
"git.sr.ht/~sircmpwn/core-go/database"
work "git.sr.ht/~sircmpwn/dowork"
)
type contextKey struct {
name string
}
var ctxKey = &contextKey{"account"}
func Middleware(queue *work.Queue) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), ctxKey, queue)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
}
// Schedules a user account deletion.
func Delete(ctx context.Context, userID int, username string) {
queue, ok := ctx.Value(ctxKey).(*work.Queue)
if !ok {
panic("No account worker for this context")
}
task := work.NewTask(func(ctx context.Context) error {
log.Printf("Processing deletion of user account %d %s", userID, username)
if err := database.WithTx(ctx, nil, func(tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, `
DELETE FROM "user" WHERE id = $1;
`, userID)
return err
}); err != nil {
return err
}
log.Printf("Deletion of user account %d %s complete", userID, username)
return nil
})
queue.Enqueue(task)
log.Printf("Enqueued deletion of user account %d %s", userID, username)
}

View File

@ -4,6 +4,7 @@ go 1.17
require (
git.sr.ht/~sircmpwn/core-go v0.0.0-20221025082458-3e69641ef307
git.sr.ht/~sircmpwn/dowork v0.0.0-20210820133136-d3970e97def3
github.com/99designs/gqlgen v0.17.20
github.com/Masterminds/squirrel v1.5.0
github.com/go-chi/chi v4.1.2+incompatible
@ -14,7 +15,6 @@ require (
)
require (
git.sr.ht/~sircmpwn/dowork v0.0.0-20210820133136-d3970e97def3 // indirect
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3 // indirect
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210227202403-5dae5c48f917 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect

View File

@ -21,6 +21,12 @@ directive @private on FIELD_DEFINITION
"Used to provide a human-friendly description of an access scope."
directive @scopehelp(details: String!) on ENUM_VALUE
"""
This used to decorate fields which are for internal use, and are not
available to normal API users.
"""
directive @internal on FIELD_DEFINITION
enum AccessScope {
PROFILE @scopehelp(details: "profile information")
PASTES @scopehelp(details: "pastes")
@ -288,4 +294,9 @@ type Mutation {
unexpected behavior with the third-party integration.
"""
deleteUserWebhook(id: Int!): WebhookSubscription!
"""
Deletes the authenticated user's account. Internal use only.
"""
deleteUser: Int! @internal
}

View File

@ -23,6 +23,7 @@ import (
"git.sr.ht/~sircmpwn/core-go/server"
"git.sr.ht/~sircmpwn/core-go/valid"
corewebhooks "git.sr.ht/~sircmpwn/core-go/webhooks"
"git.sr.ht/~sircmpwn/paste.sr.ht/api/account"
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/api"
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/model"
"git.sr.ht/~sircmpwn/paste.sr.ht/api/loaders"
@ -350,6 +351,13 @@ func (r *mutationResolver) DeleteUserWebhook(ctx context.Context, id int) (model
return &sub, nil
}
// DeleteUser is the resolver for the deleteUser field.
func (r *mutationResolver) DeleteUser(ctx context.Context) (int, error) {
user := auth.ForContext(ctx)
account.Delete(ctx, user.UserID, user.Username)
return user.UserID, nil
}
// Files is the resolver for the files field.
func (r *pasteResolver) Files(ctx context.Context, obj *model.Paste) ([]*model.File, error) {
var files []*model.File

View File

@ -9,9 +9,11 @@ import (
"git.sr.ht/~sircmpwn/core-go/database"
"git.sr.ht/~sircmpwn/core-go/server"
"git.sr.ht/~sircmpwn/core-go/webhooks"
work "git.sr.ht/~sircmpwn/dowork"
"github.com/99designs/gqlgen/graphql"
"github.com/go-chi/chi"
"git.sr.ht/~sircmpwn/paste.sr.ht/api/account"
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph"
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/api"
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/model"
@ -23,6 +25,7 @@ func main() {
gqlConfig := api.Config{Resolvers: &graph.Resolver{}}
gqlConfig.Directives.Private = server.Private
gqlConfig.Directives.Internal = server.Internal
gqlConfig.Directives.Access = func(ctx context.Context, obj interface{},
next graphql.Resolver, scope model.AccessScope,
kind model.AccessKind) (interface{}, error) {
@ -35,13 +38,18 @@ func main() {
scopes[i] = s.String()
}
accountQueue := work.NewQueue("account")
webhookQueue := webhooks.NewQueue(schema)
gsrv := server.NewServer("paste.sr.ht", appConfig).
WithDefaultMiddleware().
WithMiddleware(loaders.Middleware, webhooks.Middleware(webhookQueue)).
WithMiddleware(
loaders.Middleware,
account.Middleware(accountQueue),
webhooks.Middleware(webhookQueue),
).
WithSchema(schema, scopes).
WithQueues(webhookQueue.Queue)
WithQueues(accountQueue, webhookQueue.Queue)
// Bulk transfer endpoints
gsrv.Router().Get("/query/blob/{id}", func(w http.ResponseWriter, r *http.Request) {