Files
goclaw/internal/store/browser_cookie_store.go
2026-05-24 11:27:02 +07:00

111 lines
3.4 KiB
Go

package store
import (
"context"
"errors"
"strings"
"time"
"github.com/google/uuid"
)
var (
ErrBrowserCookieEncryptionRequired = errors.New("browser cookie encryption key required")
ErrBrowserCookieTenantRequired = errors.New("browser cookie tenant_id required")
ErrBrowserCookieUserRequired = errors.New("browser cookie user_id required")
ErrBrowserCookieAgentRequired = errors.New("browser cookie agent_id required")
ErrBrowserCookieDomainRequired = errors.New("browser cookie domain required")
ErrBrowserCookieNameRequired = errors.New("browser cookie name required")
ErrBrowserCookiePathRequired = errors.New("browser cookie path required")
)
// BrowserCookieScope is the tenant/user/agent boundary for synced browser cookies.
type BrowserCookieScope struct {
TenantID uuid.UUID
UserID string
AgentID string
}
// BrowserCookie stores one selected browser cookie. Value is plaintext only at API
// and browser-application boundaries; stores encrypt it at rest.
type BrowserCookie struct {
ID uuid.UUID `json:"id" db:"id"`
TenantID uuid.UUID `json:"tenant_id" db:"tenant_id"`
UserID string `json:"user_id" db:"user_id"`
AgentID string `json:"agent_id" db:"agent_id"`
Domain string `json:"domain" db:"domain"`
Name string `json:"name" db:"name"`
Path string `json:"path" db:"path"`
Value string `json:"-" db:"-"`
Secure bool `json:"secure" db:"secure"`
HTTPOnly bool `json:"http_only" db:"http_only"`
SameSite string `json:"same_site,omitempty" db:"same_site"`
ExpiresAt *time.Time `json:"expires_at,omitempty" db:"expires_at"`
Source string `json:"source,omitempty" db:"source"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
type BrowserCookieFilter struct {
Domain string
Name string
Path string
}
type BrowserCookieStore interface {
Upsert(ctx context.Context, scope BrowserCookieScope, cookies []BrowserCookie) (int, error)
List(ctx context.Context, scope BrowserCookieScope, filter BrowserCookieFilter) ([]BrowserCookie, error)
Delete(ctx context.Context, scope BrowserCookieScope, filter BrowserCookieFilter) (int, error)
}
func BrowserCookieScopeFromContext(ctx context.Context, agentID string) BrowserCookieScope {
tid := TenantIDFromContext(ctx)
if tid == uuid.Nil {
tid = MasterTenantID
}
return BrowserCookieScope{
TenantID: tid,
UserID: CredentialUserIDFromContext(ctx),
AgentID: strings.TrimSpace(agentID),
}
}
func (s BrowserCookieScope) Validate() error {
switch {
case s.TenantID == uuid.Nil:
return ErrBrowserCookieTenantRequired
case strings.TrimSpace(s.UserID) == "":
return ErrBrowserCookieUserRequired
case strings.TrimSpace(s.AgentID) == "":
return ErrBrowserCookieAgentRequired
default:
return nil
}
}
func NormalizeBrowserCookie(c BrowserCookie) BrowserCookie {
c.Domain = strings.ToLower(strings.TrimSpace(c.Domain))
c.Name = strings.TrimSpace(c.Name)
c.Path = strings.TrimSpace(c.Path)
c.SameSite = strings.TrimSpace(c.SameSite)
c.Source = strings.TrimSpace(c.Source)
if c.Path == "" {
c.Path = "/"
}
return c
}
func ValidateBrowserCookie(c BrowserCookie) error {
c = NormalizeBrowserCookie(c)
switch {
case c.Domain == "":
return ErrBrowserCookieDomainRequired
case c.Name == "":
return ErrBrowserCookieNameRequired
case c.Path == "":
return ErrBrowserCookiePathRequired
default:
return nil
}
}