Skip to content

Commit 392fc9c

Browse files
localai-botmudlerclaude
authored
fix(auth): cascade user deletion across all owned data on PostgreSQL (#9702)
* fix(auth): cascade user deletion across all owned data on PostgreSQL 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] * chore(deps): bump LocalAGI/LocalRecall — pull in go-fitz PDF extraction 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> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3136809 commit 392fc9c

6 files changed

Lines changed: 313 additions & 186 deletions

File tree

core/http/auth/users.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package auth
2+
3+
import (
4+
"fmt"
5+
6+
"gorm.io/gorm"
7+
)
8+
9+
// DeleteUserCascade removes a user and all of their owned data.
10+
//
11+
// PostgreSQL strictly enforces every foreign key, while SQLite only enforces
12+
// them when foreign_keys=ON. So we explicitly delete every dependent row
13+
// instead of relying on `ON DELETE CASCADE`, otherwise:
14+
//
15+
// - on PostgreSQL the user delete fails with a constraint violation when
16+
// the user authored or consumed any invite codes (the InviteCode FKs are
17+
// declared without an OnDelete: CASCADE constraint), and
18+
// - usage_records have no FK at all, so they would be left orphaned in any
19+
// dialect.
20+
//
21+
// It also clears the in-memory quota cache for the user.
22+
//
23+
// Returns gorm.ErrRecordNotFound when the user does not exist.
24+
func DeleteUserCascade(db *gorm.DB, userID string) error {
25+
err := db.Transaction(func(tx *gorm.DB) error {
26+
// Drop invites authored by this user; the admin who issued them is gone.
27+
if err := tx.Where("created_by = ?", userID).Delete(&InviteCode{}).Error; err != nil {
28+
return fmt.Errorf("delete invites created by user: %w", err)
29+
}
30+
// Preserve audit trail for invites consumed by this user — null the FK.
31+
if err := tx.Model(&InviteCode{}).Where("used_by = ?", userID).Update("used_by", nil).Error; err != nil {
32+
return fmt.Errorf("clear used_by on invites: %w", err)
33+
}
34+
// Wipe collected metrics; they have no FK and would otherwise orphan.
35+
if err := tx.Where("user_id = ?", userID).Delete(&UsageRecord{}).Error; err != nil {
36+
return fmt.Errorf("delete usage records: %w", err)
37+
}
38+
// Explicit deletes for the CASCADE-backed children too — they're cheap
39+
// and keep behaviour identical across SQLite (FKs may be OFF) and
40+
// PostgreSQL.
41+
if err := tx.Where("user_id = ?", userID).Delete(&Session{}).Error; err != nil {
42+
return fmt.Errorf("delete sessions: %w", err)
43+
}
44+
if err := tx.Where("user_id = ?", userID).Delete(&UserAPIKey{}).Error; err != nil {
45+
return fmt.Errorf("delete api keys: %w", err)
46+
}
47+
if err := tx.Where("user_id = ?", userID).Delete(&UserPermission{}).Error; err != nil {
48+
return fmt.Errorf("delete permissions: %w", err)
49+
}
50+
if err := tx.Where("user_id = ?", userID).Delete(&QuotaRule{}).Error; err != nil {
51+
return fmt.Errorf("delete quota rules: %w", err)
52+
}
53+
54+
result := tx.Where("id = ?", userID).Delete(&User{})
55+
if result.Error != nil {
56+
return fmt.Errorf("delete user: %w", result.Error)
57+
}
58+
if result.RowsAffected == 0 {
59+
return gorm.ErrRecordNotFound
60+
}
61+
return nil
62+
})
63+
if err != nil {
64+
return err
65+
}
66+
67+
quotaCache.invalidateUser(userID)
68+
return nil
69+
}

core/http/auth/users_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//go:build auth
2+
3+
package auth_test
4+
5+
import (
6+
"time"
7+
8+
"github.com/google/uuid"
9+
"github.com/mudler/LocalAI/core/http/auth"
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
"gorm.io/gorm"
13+
)
14+
15+
// Regression coverage for the production bug where deleting a user from the
16+
// distributed-mode (PostgreSQL) admin UI returned "user not found" because the
17+
// old delete path ignored result.Error and left several tables uncleaned.
18+
var _ = Describe("DeleteUserCascade", Label("auth"), func() {
19+
var db *gorm.DB
20+
21+
BeforeEach(func() {
22+
db = testDB()
23+
})
24+
25+
It("returns ErrRecordNotFound when the user does not exist", func() {
26+
err := auth.DeleteUserCascade(db, uuid.New().String())
27+
Expect(err).To(Equal(gorm.ErrRecordNotFound))
28+
})
29+
30+
It("removes invite codes the user authored", func() {
31+
target := createTestUser(db, "author@test.com", auth.RoleAdmin, auth.ProviderLocal)
32+
Expect(db.Create(&auth.InviteCode{
33+
ID: uuid.New().String(), Code: "code-author-1", CodePrefix: "code-aut",
34+
CreatedBy: target.ID, ExpiresAt: time.Now().Add(time.Hour),
35+
}).Error).ToNot(HaveOccurred())
36+
37+
Expect(auth.DeleteUserCascade(db, target.ID)).ToNot(HaveOccurred())
38+
39+
var count int64
40+
db.Model(&auth.InviteCode{}).Where("created_by = ?", target.ID).Count(&count)
41+
Expect(count).To(Equal(int64(0)))
42+
})
43+
44+
It("nulls used_by on invite codes the user consumed but keeps the audit row", func() {
45+
admin := createTestUser(db, "admin-keep@test.com", auth.RoleAdmin, auth.ProviderLocal)
46+
target := createTestUser(db, "consumer@test.com", auth.RoleUser, auth.ProviderLocal)
47+
48+
usedBy := target.ID
49+
now := time.Now()
50+
invite := &auth.InviteCode{
51+
ID: uuid.New().String(), Code: "code-used-1", CodePrefix: "code-use",
52+
CreatedBy: admin.ID, UsedBy: &usedBy, UsedAt: &now,
53+
ExpiresAt: now.Add(time.Hour),
54+
}
55+
Expect(db.Create(invite).Error).ToNot(HaveOccurred())
56+
57+
Expect(auth.DeleteUserCascade(db, target.ID)).ToNot(HaveOccurred())
58+
59+
var refreshed auth.InviteCode
60+
Expect(db.First(&refreshed, "id = ?", invite.ID).Error).ToNot(HaveOccurred())
61+
Expect(refreshed.UsedBy).To(BeNil(), "used_by should be cleared so the FK no longer points to the deleted user")
62+
})
63+
64+
It("wipes sessions, api keys, permissions, quotas, and usage metrics", func() {
65+
target := createTestUser(db, "owns-data@test.com", auth.RoleUser, auth.ProviderLocal)
66+
67+
_ = createTestSession(db, target.ID)
68+
_, _, err := auth.CreateAPIKey(db, target.ID, "k1", auth.RoleUser, "", nil)
69+
Expect(err).ToNot(HaveOccurred())
70+
Expect(auth.UpdateUserPermissions(db, target.ID, auth.PermissionMap{auth.FeatureChat: true})).ToNot(HaveOccurred())
71+
max := int64(100)
72+
_, err = auth.CreateOrUpdateQuotaRule(db, target.ID, "", &max, nil, 3600)
73+
Expect(err).ToNot(HaveOccurred())
74+
Expect(auth.RecordUsage(db, &auth.UsageRecord{
75+
UserID: target.ID, UserName: target.Name, Model: "test-model",
76+
Endpoint: "/v1/chat/completions", PromptTokens: 5, CompletionTokens: 10, TotalTokens: 15,
77+
})).ToNot(HaveOccurred())
78+
79+
Expect(auth.DeleteUserCascade(db, target.ID)).ToNot(HaveOccurred())
80+
81+
var sessions, keys, perms, quotas, usage int64
82+
db.Model(&auth.Session{}).Where("user_id = ?", target.ID).Count(&sessions)
83+
db.Model(&auth.UserAPIKey{}).Where("user_id = ?", target.ID).Count(&keys)
84+
db.Model(&auth.UserPermission{}).Where("user_id = ?", target.ID).Count(&perms)
85+
db.Model(&auth.QuotaRule{}).Where("user_id = ?", target.ID).Count(&quotas)
86+
db.Model(&auth.UsageRecord{}).Where("user_id = ?", target.ID).Count(&usage)
87+
88+
Expect(sessions).To(Equal(int64(0)))
89+
Expect(keys).To(Equal(int64(0)))
90+
Expect(perms).To(Equal(int64(0)))
91+
Expect(quotas).To(Equal(int64(0)))
92+
Expect(usage).To(Equal(int64(0)), "usage metrics must be removed alongside the user")
93+
})
94+
95+
It("succeeds with foreign keys enforced — the production failure mode", func() {
96+
// Mirror PostgreSQL's strict FK behavior on the SQLite test DB. Without
97+
// the cleanup of invite_codes.created_by, the engine would reject the
98+
// user delete with a constraint violation, which the old handler then
99+
// surfaced as a misleading 404.
100+
Expect(db.Exec("PRAGMA foreign_keys = ON").Error).ToNot(HaveOccurred())
101+
102+
target := createTestUser(db, "fk-author@test.com", auth.RoleAdmin, auth.ProviderLocal)
103+
Expect(db.Create(&auth.InviteCode{
104+
ID: uuid.New().String(), Code: "code-fk-1", CodePrefix: "code-fk1",
105+
CreatedBy: target.ID, ExpiresAt: time.Now().Add(time.Hour),
106+
}).Error).ToNot(HaveOccurred())
107+
108+
Expect(auth.DeleteUserCascade(db, target.ID)).ToNot(HaveOccurred())
109+
110+
var users int64
111+
db.Model(&auth.User{}).Where("id = ?", target.ID).Count(&users)
112+
Expect(users).To(Equal(int64(0)))
113+
})
114+
})

core/http/routes/auth.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -933,13 +933,11 @@ func RegisterAuthRoutes(e *echo.Echo, app *application.Application) {
933933
return c.JSON(http.StatusBadRequest, map[string]string{"error": "cannot delete yourself"})
934934
}
935935

936-
// Cascade: delete sessions and API keys
937-
db.Where("user_id = ?", targetID).Delete(&auth.Session{})
938-
db.Where("user_id = ?", targetID).Delete(&auth.UserAPIKey{})
939-
940-
result := db.Where("id = ?", targetID).Delete(&auth.User{})
941-
if result.RowsAffected == 0 {
942-
return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"})
936+
if err := auth.DeleteUserCascade(db, targetID); err != nil {
937+
if err == gorm.ErrRecordNotFound {
938+
return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"})
939+
}
940+
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to delete user: " + err.Error()})
943941
}
944942

945943
return c.JSON(http.StatusOK, map[string]string{"message": "user deleted"})

core/http/routes/auth_test.go

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"net/http/httptest"
1010
"strings"
11+
"time"
1112

1213
"github.com/google/uuid"
1314
"github.com/labstack/echo/v4"
@@ -276,11 +277,11 @@ func newTestAuthApp(db *gorm.DB, appConfig *config.ApplicationConfig) *echo.Echo
276277
if currentUser.ID == targetID {
277278
return c.JSON(http.StatusBadRequest, map[string]string{"error": "cannot delete yourself"})
278279
}
279-
db.Where("user_id = ?", targetID).Delete(&auth.Session{})
280-
db.Where("user_id = ?", targetID).Delete(&auth.UserAPIKey{})
281-
result := db.Where("id = ?", targetID).Delete(&auth.User{})
282-
if result.RowsAffected == 0 {
283-
return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"})
280+
if err := auth.DeleteUserCascade(db, targetID); err != nil {
281+
if err == gorm.ErrRecordNotFound {
282+
return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"})
283+
}
284+
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to delete user: " + err.Error()})
284285
}
285286
return c.JSON(http.StatusOK, map[string]string{"message": "user deleted"})
286287
}, adminMw)
@@ -686,6 +687,107 @@ var _ = Describe("Auth Routes", Label("auth"), func() {
686687
rec := doAuthRequest(app, "DELETE", "/api/auth/admin/users/"+target.ID, nil, withSession(sessionID))
687688
Expect(rec.Code).To(Equal(http.StatusForbidden))
688689
})
690+
691+
// Regression coverage for the production bug: in distributed mode the
692+
// auth DB is PostgreSQL, which strictly enforces foreign keys. The old
693+
// handler did not clean up invite_codes, user_permissions, quota_rules,
694+
// or usage_records, which either caused FK violations (surfaced as a
695+
// misleading 404 "user not found") or left orphan rows after delete.
696+
It("removes invite codes the user authored", func() {
697+
admin := createRouteTestUser(db, "admin-inv-author@test.com", auth.RoleAdmin)
698+
target := createRouteTestUser(db, "deletes-author@test.com", auth.RoleAdmin)
699+
Expect(db.Create(&auth.InviteCode{
700+
ID: uuid.New().String(), Code: "code-authored", CodePrefix: "code-aut",
701+
CreatedBy: target.ID, ExpiresAt: time.Now().Add(time.Hour),
702+
}).Error).ToNot(HaveOccurred())
703+
sessionID, _ := auth.CreateSession(db, admin.ID, "")
704+
app := newTestAuthApp(db, appConfig)
705+
706+
rec := doAuthRequest(app, "DELETE", "/api/auth/admin/users/"+target.ID, nil, withSession(sessionID))
707+
Expect(rec.Code).To(Equal(http.StatusOK))
708+
709+
var count int64
710+
db.Model(&auth.InviteCode{}).Where("created_by = ?", target.ID).Count(&count)
711+
Expect(count).To(Equal(int64(0)))
712+
})
713+
714+
It("nulls used_by on invite codes the user consumed", func() {
715+
admin := createRouteTestUser(db, "admin-inv-consumer@test.com", auth.RoleAdmin)
716+
target := createRouteTestUser(db, "deletes-consumer@test.com", auth.RoleUser)
717+
usedBy := target.ID
718+
now := time.Now()
719+
Expect(db.Create(&auth.InviteCode{
720+
ID: uuid.New().String(), Code: "code-used", CodePrefix: "code-use",
721+
CreatedBy: admin.ID, UsedBy: &usedBy, UsedAt: &now,
722+
ExpiresAt: now.Add(time.Hour),
723+
}).Error).ToNot(HaveOccurred())
724+
sessionID, _ := auth.CreateSession(db, admin.ID, "")
725+
app := newTestAuthApp(db, appConfig)
726+
727+
rec := doAuthRequest(app, "DELETE", "/api/auth/admin/users/"+target.ID, nil, withSession(sessionID))
728+
Expect(rec.Code).To(Equal(http.StatusOK))
729+
730+
// Audit row stays, but no longer points to the deleted user.
731+
var stale int64
732+
db.Model(&auth.InviteCode{}).Where("used_by = ?", target.ID).Count(&stale)
733+
Expect(stale).To(Equal(int64(0)))
734+
var total int64
735+
db.Model(&auth.InviteCode{}).Where("created_by = ?", admin.ID).Count(&total)
736+
Expect(total).To(Equal(int64(1)))
737+
})
738+
739+
It("wipes permissions, quotas, and usage metrics", func() {
740+
admin := createRouteTestUser(db, "admin-clean@test.com", auth.RoleAdmin)
741+
target := createRouteTestUser(db, "deletes-clean@test.com", auth.RoleUser)
742+
743+
Expect(auth.UpdateUserPermissions(db, target.ID, auth.PermissionMap{auth.FeatureChat: true})).ToNot(HaveOccurred())
744+
max := int64(100)
745+
_, err := auth.CreateOrUpdateQuotaRule(db, target.ID, "", &max, nil, 3600)
746+
Expect(err).ToNot(HaveOccurred())
747+
Expect(auth.RecordUsage(db, &auth.UsageRecord{
748+
UserID: target.ID, UserName: target.Name, Model: "test-model",
749+
Endpoint: "/v1/chat/completions", PromptTokens: 5, CompletionTokens: 10, TotalTokens: 15,
750+
})).ToNot(HaveOccurred())
751+
752+
sessionID, _ := auth.CreateSession(db, admin.ID, "")
753+
app := newTestAuthApp(db, appConfig)
754+
755+
rec := doAuthRequest(app, "DELETE", "/api/auth/admin/users/"+target.ID, nil, withSession(sessionID))
756+
Expect(rec.Code).To(Equal(http.StatusOK))
757+
758+
var perms, quotas, usage int64
759+
db.Model(&auth.UserPermission{}).Where("user_id = ?", target.ID).Count(&perms)
760+
db.Model(&auth.QuotaRule{}).Where("user_id = ?", target.ID).Count(&quotas)
761+
db.Model(&auth.UsageRecord{}).Where("user_id = ?", target.ID).Count(&usage)
762+
Expect(perms).To(Equal(int64(0)))
763+
Expect(quotas).To(Equal(int64(0)))
764+
Expect(usage).To(Equal(int64(0)))
765+
})
766+
767+
It("returns 200 even when foreign keys are enforced and the user authored invites", func() {
768+
// Mirror PostgreSQL's strict FK behavior on the SQLite test DB. This
769+
// exercises exactly the production failure: without the cleanup,
770+
// the user delete would be rejected by the engine and the handler
771+
// would surface a misleading 404.
772+
Expect(db.Exec("PRAGMA foreign_keys = ON").Error).ToNot(HaveOccurred())
773+
774+
admin := createRouteTestUser(db, "admin-fk@test.com", auth.RoleAdmin)
775+
target := createRouteTestUser(db, "deletes-fk@test.com", auth.RoleAdmin)
776+
Expect(db.Create(&auth.InviteCode{
777+
ID: uuid.New().String(), Code: "code-fk", CodePrefix: "code-fk1",
778+
CreatedBy: target.ID, ExpiresAt: time.Now().Add(time.Hour),
779+
}).Error).ToNot(HaveOccurred())
780+
781+
sessionID, _ := auth.CreateSession(db, admin.ID, "")
782+
app := newTestAuthApp(db, appConfig)
783+
784+
rec := doAuthRequest(app, "DELETE", "/api/auth/admin/users/"+target.ID, nil, withSession(sessionID))
785+
Expect(rec.Code).To(Equal(http.StatusOK), "body=%s", rec.Body.String())
786+
787+
var users int64
788+
db.Model(&auth.User{}).Where("id = ?", target.ID).Count(&users)
789+
Expect(users).To(Equal(int64(0)))
790+
})
689791
})
690792

691793
Context("POST /api/auth/register", func() {

go.mod

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,11 @@ require (
9494
github.com/bahlo/generic-list-go v0.2.0 // indirect
9595
github.com/buger/jsonparser v1.1.1 // indirect
9696
github.com/dunglas/httpsfv v1.1.0 // indirect
97+
github.com/gen2brain/go-fitz v1.24.15 // indirect
9798
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
9899
github.com/jinzhu/inflection v1.0.0 // indirect
99100
github.com/jinzhu/now v1.1.5 // indirect
101+
github.com/jupiterrider/ffi v0.5.0 // indirect
100102
github.com/mattn/go-sqlite3 v1.14.24 // indirect
101103
github.com/moby/moby/api v1.54.1 // indirect
102104
github.com/moby/moby/client v0.4.0 // indirect
@@ -142,7 +144,6 @@ require (
142144
github.com/bwmarrin/discordgo v0.29.0 // indirect
143145
github.com/cloudflare/circl v1.6.1 // indirect
144146
github.com/cyphar/filepath-securejoin v0.5.1 // indirect
145-
github.com/dslipak/pdf v0.0.2 // indirect
146147
github.com/emersion/go-imap/v2 v2.0.0-beta.5 // indirect
147148
github.com/emersion/go-message v0.18.2 // indirect
148149
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
@@ -170,8 +171,8 @@ require (
170171
github.com/kevinburke/ssh_config v1.2.0 // indirect
171172
github.com/labstack/gommon v0.4.2 // indirect
172173
github.com/mschoch/smat v0.2.0 // indirect
173-
github.com/mudler/LocalAGI v0.0.0-20260504165100-e83bf515d010
174-
github.com/mudler/localrecall v0.5.10-0.20260504162944-6138c1f535ab // indirect
174+
github.com/mudler/LocalAGI v0.0.0-20260506230719-facd8881b135
175+
github.com/mudler/localrecall v0.6.0 // indirect
175176
github.com/mudler/skillserver v0.0.6
176177
github.com/olekukonko/tablewriter v0.0.5 // indirect
177178
github.com/oxffaa/gopher-parse-sitemap v0.0.0-20191021113419-005d2eb1def4 // indirect
@@ -254,7 +255,6 @@ require (
254255
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
255256
github.com/otiai10/mint v1.6.3 // indirect
256257
github.com/pion/datachannel v1.6.0 // indirect
257-
github.com/pion/dtls/v2 v2.2.12 // indirect
258258
github.com/pion/dtls/v3 v3.1.2 // indirect
259259
github.com/pion/ice/v4 v4.2.2 // indirect
260260
github.com/pion/interceptor v0.1.44 // indirect
@@ -266,9 +266,7 @@ require (
266266
github.com/pion/sctp v1.9.4 // indirect
267267
github.com/pion/sdp/v3 v3.0.18 // indirect
268268
github.com/pion/srtp/v3 v3.0.10 // indirect
269-
github.com/pion/stun v0.6.1 // indirect
270269
github.com/pion/stun/v3 v3.1.1 // indirect
271-
github.com/pion/transport/v2 v2.2.10 // indirect
272270
github.com/pion/turn/v4 v4.1.4 // indirect
273271
github.com/pion/webrtc/v4 v4.2.11
274272
github.com/prometheus/otlptranslator v1.0.0 // indirect
@@ -324,7 +322,6 @@ require (
324322
github.com/docker/go-units v0.5.0 // indirect
325323
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
326324
github.com/flynn/noise v1.1.0 // indirect
327-
github.com/francoispqt/gojay v1.2.13 // indirect
328325
github.com/go-audio/audio v1.0.0
329326
github.com/go-audio/riff v1.0.0 // indirect
330327
github.com/go-logr/logr v1.4.3 // indirect

0 commit comments

Comments
 (0)