capsule AI-native Unix-like composition layer

src/server/internal/api/anonymous_owner.go

1,804 bytes · 74 lines · capsule://quake0day/[email protected] raw on github

package api

import (
	"crypto/rand"
	"encoding/base64"
	"errors"
	"net/http"
	"strings"
	"time"
)

const (
	anonymousOwnerCookieName = "cyberverse_anonymous_owner"
	anonymousOwnerTTL        = 180 * 24 * time.Hour
	anonymousOwnerPrefix     = "anon:"
)

func (r *Router) ensureAnonymousOwner(w http.ResponseWriter, req *http.Request) (string, error) {
	if ownerID, ok := anonymousOwnerFromRequest(req); ok {
		return ownerID, nil
	}
	token, err := randomOwnerToken()
	if err != nil {
		return "", err
	}
	http.SetCookie(w, anonymousOwnerCookie(req, token))
	return anonymousOwnerPrefix + token, nil
}

func anonymousOwnerFromRequest(req *http.Request) (string, bool) {
	cookie, err := req.Cookie(anonymousOwnerCookieName)
	if err != nil || cookie.Value == "" {
		return "", false
	}
	token := strings.TrimSpace(cookie.Value)
	if !validOwnerToken(token) {
		return "", false
	}
	return anonymousOwnerPrefix + token, true
}

func randomOwnerToken() (string, error) {
	buf := make([]byte, 32)
	if _, err := rand.Read(buf); err != nil {
		return "", errors.New("failed to generate anonymous owner")
	}
	return base64.RawURLEncoding.EncodeToString(buf), nil
}

func validOwnerToken(token string) bool {
	raw, err := base64.RawURLEncoding.DecodeString(token)
	return err == nil && len(raw) == 32
}

func anonymousOwnerCookie(req *http.Request, token string) *http.Cookie {
	return &http.Cookie{
		Name:     anonymousOwnerCookieName,
		Value:    token,
		Path:     "/",
		Expires:  time.Now().Add(anonymousOwnerTTL),
		MaxAge:   int(anonymousOwnerTTL.Seconds()),
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
		Secure:   requestIsHTTPS(req),
	}
}

func requestIsHTTPS(req *http.Request) bool {
	if req.TLS != nil {
		return true
	}
	return strings.EqualFold(req.Header.Get("X-Forwarded-Proto"), "https")
}