feat: auth service + security audit fixes + cleanup legacy services

Major changes:
- Add auth-svc: JWT auth, register/login/refresh, password reset
- Add auth UI: modals, pages (/login, /register, /forgot-password)
- Add usage tracking (usage_metrics table, daily limits)
- Add tiered rate limiting (free/pro/business)
- Add LLM usage limits per tier

Security fixes:
- All repos now require userID for Update/Delete operations
- JWT middleware in chat-svc, llm-svc, agent-svc, discover-svc
- ErrNotFound/ErrForbidden errors for proper access control

Cleanup:
- Remove legacy TypeScript services/ directory
- Remove computer-svc (to be reimplemented)
- Remove old deploy/docker configs

New files:
- backend/cmd/auth-svc/main.go
- backend/internal/auth/{types,repository}.go
- backend/internal/usage/{types,repository}.go
- backend/pkg/middleware/{llm_limits,ratelimit_tiered}.go
- backend/webui/src/components/auth/*
- backend/webui/src/app/(auth)/*

Made-with: Cursor
This commit is contained in:
home
2026-02-28 01:33:49 +03:00
parent 120fbbaafb
commit a0e3748dde
523 changed files with 10776 additions and 59630 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gooseek/backend/pkg/config"
"github.com/gooseek/backend/pkg/middleware"
)
var svcURLs map[string]string
@@ -25,6 +26,7 @@ func main() {
}
svcURLs = map[string]string{
"auth": cfg.AuthSvcURL,
"chat": cfg.ChatSvcURL,
"agents": cfg.AgentSvcURL,
"search": cfg.SearchSvcURL,
@@ -36,7 +38,7 @@ func main() {
"discover": cfg.DiscoverSvcURL,
"finance": cfg.FinanceHeatmapURL,
"learning": cfg.LearningSvcURL,
"computer": cfg.ComputerSvcURL,
"admin": cfg.AdminSvcURL,
}
app := fiber.New(fiber.Config{
@@ -54,6 +56,21 @@ func main() {
AllowMethods: "GET, POST, PUT, PATCH, DELETE, OPTIONS",
}))
app.Use(middleware.JWT(middleware.JWTConfig{
Secret: cfg.JWTSecret,
AuthSvcURL: cfg.AuthSvcURL,
AllowGuest: true,
}))
app.Use(middleware.TieredRateLimit(middleware.TieredRateLimitConfig{
Tiers: map[string]middleware.TierConfig{
"free": {Max: 60, Window: time.Minute},
"pro": {Max: 300, Window: time.Minute},
"business": {Max: 1000, Window: time.Minute},
},
DefaultTier: "free",
}))
app.Get("/health", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"status": "ok"})
})
@@ -72,6 +89,8 @@ func main() {
func getTarget(path string) (base, rewrite string) {
switch {
case strings.HasPrefix(path, "/api/v1/auth"):
return svcURLs["auth"], path
case path == "/api/chat" || strings.HasPrefix(path, "/api/chat?"):
return svcURLs["chat"], "/api/v1/chat"
case strings.HasPrefix(path, "/api/v1/agents"):
@@ -102,8 +121,8 @@ func getTarget(path string) (base, rewrite string) {
return svcURLs["finance"], path
case strings.HasPrefix(path, "/api/v1/learning"):
return svcURLs["learning"], path
case strings.HasPrefix(path, "/api/v1/computer"):
return svcURLs["computer"], path
case strings.HasPrefix(path, "/api/v1/admin"):
return svcURLs["admin"], path
default:
return "", ""
}
@@ -195,11 +214,44 @@ func handleProxy(c *fiber.Ctx) error {
}
}
client := &http.Client{Timeout: time.Minute}
isSSE := strings.Contains(path, "/stream") ||
c.Get("Accept") == "text/event-stream"
timeout := time.Minute
if isSSE {
timeout = 30 * time.Minute
}
client := &http.Client{Timeout: timeout}
resp, err := client.Do(req)
if err != nil {
return handleFallback(c, path)
}
if isSSE && resp.Header.Get("Content-Type") == "text/event-stream" {
c.Set("Content-Type", "text/event-stream")
c.Set("Cache-Control", "no-cache")
c.Set("Connection", "keep-alive")
c.Set("Transfer-Encoding", "chunked")
c.Set("X-Accel-Buffering", "no")
c.Context().SetBodyStreamWriter(func(w *bufio.Writer) {
defer resp.Body.Close()
buf := make([]byte, 4096)
for {
n, readErr := resp.Body.Read(buf)
if n > 0 {
w.Write(buf[:n])
w.Flush()
}
if readErr != nil {
return
}
}
})
return nil
}
defer resp.Body.Close()
for _, h := range []string{"Content-Type", "Cache-Control", "Set-Cookie"} {