Files
backend/internal/server/chat_middleware.go

72 lines
1.9 KiB
Go
Raw Permalink Normal View History

package server
import (
"errors"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"code.littlelan.cn/CarrotAssistant/backend/internal/security"
"code.littlelan.cn/CarrotAssistant/backend/internal/store"
)
// RequireAppToken guards the public /api/v1 chat routes. It expects an
// X-App-Token header whose SHA-256 hash matches an App row. The matched App
// is placed on the context as "app".
func (d Deps) RequireAppToken() gin.HandlerFunc {
return func(c *gin.Context) {
raw := strings.TrimSpace(c.GetHeader("X-App-Token"))
if raw == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "缺少 X-App-Token"})
return
}
hash := security.HashToken(raw)
var app store.App
err := d.DB.Where("token_hash = ? AND status = ?", hash, "active").First(&app).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "无效的 token"})
return
}
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "查询失败"})
return
}
c.Set("app", &app)
c.Next()
}
}
// appCtx returns the App placed by RequireAppToken.
func appCtx(c *gin.Context) *store.App {
v, _ := c.Get("app")
if v == nil {
return nil
}
return v.(*store.App)
}
// createSession persists a new session row and returns it.
func (d Deps) createSession(appID int64, title string) (*store.Session, error) {
id, err := newUUID()
if err != nil {
return nil, err
}
s := &store.Session{ID: id, AppID: appID, Title: title}
if err := d.DB.Create(s).Error; err != nil {
return nil, err
}
return s, nil
}
// loadSessionOwnedBy returns a session only if it belongs to appID.
func (d Deps) loadSessionOwnedBy(sessionID string, appID int64) (*store.Session, error) {
var s store.Session
err := d.DB.Where("id = ? AND app_id = ?", sessionID, appID).First(&s).Error
if err != nil {
return nil, err
}
return &s, nil
}