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:
@@ -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"} {
|
||||
|
||||
Reference in New Issue
Block a user