fix(auth): cascade user deletion across all owned data on PostgreSQL#9702
Open
localai-bot wants to merge 2 commits intomasterfrom
Open
fix(auth): cascade user deletion across all owned data on PostgreSQL#9702localai-bot wants to merge 2 commits intomasterfrom
localai-bot wants to merge 2 commits intomasterfrom
Conversation
Deleting a user from the admin UI in distributed mode (PostgreSQL auth DB) returned "user not found" even when the user clearly existed. The old handler ignored result.Error and only checked RowsAffected, so a foreign-key constraint violation surfaced as a misleading 404. Two issues drove this: 1. invite_codes.created_by / used_by reference users(id) but the InviteCode model declared the FKs without ON DELETE CASCADE. On PostgreSQL the engine therefore rejected the user delete with NO ACTION whenever the user had ever issued or consumed an invite. On SQLite (default in single-node mode) FKs are not enforced, so the bug never appeared there. 2. Several owned tables were never cleaned up regardless of dialect: user_permissions and quota_rules relied on CASCADE that does not fire under SQLite, and usage_records have no FK at all and were left orphaned in every dialect. Introduce auth.DeleteUserCascade which runs the full cleanup in a single transaction: drop invites authored by the user, NULL used_by on invites they consumed (preserves the audit trail), and explicitly wipe sessions, API keys, permissions, quota rules, and usage metrics before deleting the user. The in-memory quota cache is invalidated after commit so a recreated user with the same id never sees stale entries. The HTTP handler now maps the helper's errors to proper status codes — real failures surface as 500 with the cause instead of being swallowed as "not found". Add Ginkgo regression coverage in core/http/auth/users_test.go and core/http/routes/auth_test.go covering invite cleanup, used_by null-out, full data wipe, and the FK-enforced original failure mode (via PRAGMA foreign_keys=ON to mirror PostgreSQL behavior on SQLite). Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-7 [Claude Code]
Pulls LocalAGI@main (facd888) and LocalRecall@v0.6.0. The latter swaps PDF text extraction from dslipak/pdf to gen2brain/go-fitz (libmupdf bindings) and wraps it in a 60s goroutine timeout — previously certain PDFs (broken xref tables, encrypted, image-only without OCR) would hang indefinitely inside r.GetPlainText() and poison the upload queue. Pure dep bump, no LocalAI source changes. Indirect graph picks up go-fitz + purego + ffi; drops dslipak/pdf. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
In distributed mode the auth DB is PostgreSQL, which strictly enforces foreign keys. Deleting a user from the admin UI returned "Failed to delete user: user not found" even when the user clearly existed.
Two issues:
invite_codes.created_by/used_byreferenceusers(id)but theInviteCodemodel declares the FKs withoutON DELETE CASCADE. PostgreSQL therefore rejected the user delete withNO ACTIONwhenever the user had ever issued or consumed an invite. On SQLite (single-node default) FKs are not enforced, so the bug never showed up there.result.Errorand only checkedRowsAffected, so the FK violation surfaced as a misleading 404.user_permissionsandquota_rulesrelied onCASCADEthat doesn't fire under SQLite, andusage_recordshave no FK at all and were left orphaned in every dialect.Fix
Introduce
auth.DeleteUserCascaderunning the full cleanup in a single transaction:NULLused_byon invites they consumed (preserves the audit trail),The HTTP handler now maps the helper's errors to proper status codes — real failures surface as 500 with the cause instead of being swallowed as 404.
Test plan
core/http/auth/users_test.gocover: ErrRecordNotFound on missing user, invite-author cleanup,used_bynull-out (audit row preserved), full data wipe (sessions / api keys / permissions / quotas / usage metrics), and the FK-enforced original failure mode viaPRAGMA foreign_keys=ONto mirror PostgreSQL behavior on SQLite.core/http/routes/auth_test.go(build-clean, but theroutespackage currently has an unrelated build break on master from feat: support word-level timestamps for faster-whisper #9621 —TranscriptSegment.Wordsis in the .proto but not in the generatedpb.go).Refs the user-not-found bug observed on the distributed PostgreSQL auth deployment.