From e91109d69c4d6b16195a494da80720243e8982e0 Mon Sep 17 00:00:00 2001 From: pptx704 Date: Thu, 16 Apr 2026 05:29:02 +0600 Subject: [PATCH] Fix API key cleanup on user deactivation and build archive race condition Delete all API keys created by a user when their account is disabled, deleted, or soft-deleted. Store build archives before enqueuing to Redis so workers never dequeue a build with missing files. --- db/queries/api_keys.sql | 3 +++ internal/api/handlers_me.go | 4 ++++ pkg/db/api_keys.sql.go | 9 +++++++++ pkg/service/build.go | 12 ++++++------ pkg/service/user.go | 6 ++++++ 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/db/queries/api_keys.sql b/db/queries/api_keys.sql index be064a1..f57bf5b 100644 --- a/db/queries/api_keys.sql +++ b/db/queries/api_keys.sql @@ -25,3 +25,6 @@ UPDATE team_api_keys SET last_used = NOW() WHERE id = $1; -- name: DeleteAPIKeysByTeam :exec DELETE FROM team_api_keys WHERE team_id = $1; + +-- name: DeleteAPIKeysByCreator :exec +DELETE FROM team_api_keys WHERE created_by = $1; diff --git a/internal/api/handlers_me.go b/internal/api/handlers_me.go index aefb7d7..c70e302 100644 --- a/internal/api/handlers_me.go +++ b/internal/api/handlers_me.go @@ -524,6 +524,10 @@ func (h *meHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) { } } + if err := h.db.DeleteAPIKeysByCreator(ctx, ac.UserID); err != nil { + slog.Warn("account delete: failed to delete user's API keys", "error", err) + } + if err := h.db.SoftDeleteUser(ctx, ac.UserID); err != nil { writeError(w, http.StatusInternalServerError, "db_error", "failed to delete account") return diff --git a/pkg/db/api_keys.sql.go b/pkg/db/api_keys.sql.go index 55f1bce..b157931 100644 --- a/pkg/db/api_keys.sql.go +++ b/pkg/db/api_keys.sql.go @@ -25,6 +25,15 @@ func (q *Queries) DeleteAPIKey(ctx context.Context, arg DeleteAPIKeyParams) erro return err } +const deleteAPIKeysByCreator = `-- name: DeleteAPIKeysByCreator :exec +DELETE FROM team_api_keys WHERE created_by = $1 +` + +func (q *Queries) DeleteAPIKeysByCreator(ctx context.Context, createdBy pgtype.UUID) error { + _, err := q.db.Exec(ctx, deleteAPIKeysByCreator, createdBy) + return err +} + const deleteAPIKeysByTeam = `-- name: DeleteAPIKeysByTeam :exec DELETE FROM team_api_keys WHERE team_id = $1 ` diff --git a/pkg/service/build.go b/pkg/service/build.go index 70e67f7..bdb1620 100644 --- a/pkg/service/build.go +++ b/pkg/service/build.go @@ -139,16 +139,16 @@ func (s *BuildService) Create(ctx context.Context, p BuildCreateParams) (db.Temp return db.TemplateBuild{}, fmt.Errorf("insert build: %w", err) } - // Enqueue build ID (as formatted string) to Redis for workers to pick up. - if err := s.Redis.RPush(ctx, buildQueueKey, buildIDStr).Err(); err != nil { - return db.TemplateBuild{}, fmt.Errorf("enqueue build: %w", err) - } - - // Store archive for the worker if provided. + // Store archive before enqueue so the worker never dequeues without files. if len(p.Archive) > 0 { s.storeArchive(buildIDStr, p.Archive) } + if err := s.Redis.RPush(ctx, buildQueueKey, buildIDStr).Err(); err != nil { + s.takeArchive(buildIDStr) // clean up on enqueue failure + return db.TemplateBuild{}, fmt.Errorf("enqueue build: %w", err) + } + return build, nil } diff --git a/pkg/service/user.go b/pkg/service/user.go index db687c0..9a3a66c 100644 --- a/pkg/service/user.go +++ b/pkg/service/user.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "log/slog" "time" "github.com/jackc/pgx/v5/pgtype" @@ -66,5 +67,10 @@ func (s *UserService) SetUserStatus(ctx context.Context, userID pgtype.UUID, sta }); err != nil { return fmt.Errorf("set user status: %w", err) } + if status == "disabled" || status == "deleted" { + if err := s.DB.DeleteAPIKeysByCreator(ctx, userID); err != nil { + slog.Warn("failed to delete API keys for deactivated user", "user_id", userID, "error", err) + } + } return nil }