api

package
v0.0.36 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 1, 2026 License: AGPL-3.0 Imports: 33 Imported by: 0

Documentation

Index

Constants

View Source
const (
	KeychainService = "focusd-engine"
	KeychainUser    = "auth-token"
)

Variables

This section is empty.

Functions

func MintToken

func MintToken(user User, role string) (string, error)

MintToken creates a new encrypted PASETO token

func NewAuthInterceptor

func NewAuthInterceptor(gormDB *gorm.DB) connect.Interceptor

NewAuthInterceptor creates a new authentication interceptor

func NewClient

func NewClient(baseURL string, interceptors ...connect.Interceptor) apiv1connect.ApiServiceClient

NewClient creates a new ApiServiceClient with signing/authentication handling. The signing interceptor handles token management and automatic refresh on expiry.

func NewHTTP2Client

func NewHTTP2Client() *http.Client

NewHTTP2Client creates an HTTP client configured for HTTP/2 cleartext (h2c). This is required for ConnectRPC streaming to work properly.

The default Go http.Client uses HTTP/1.1, which causes HTTP 505 errors when attempting to use bidirectional streaming with ConnectRPC.

Example usage:

httpClient := api.NewHTTP2Client()
apiClient := apiv1connect.NewApiServiceClient(
	httpClient,
	"http://localhost:8080",
)

func NewHTTP2ClientWithTimeout

func NewHTTP2ClientWithTimeout(timeout time.Duration) *http.Client

NewHTTP2ClientWithTimeout creates an HTTP/2 client with a custom timeout.

func NewPolarWebhookHandler

func NewPolarWebhookHandler(s *ServiceImpl) http.HandlerFunc

NewPolarWebhookHandler creates an HTTP handler for Polar.sh webhooks. It verifies the webhook signature using the Standard Webhooks specification and dispatches events to the appropriate handler methods.

Required environment variable:

  • POLAR_WEBHOOK_SECRET: The webhook signing secret configured in Polar.sh dashboard

Example curl request for testing (replace with actual values):

curl -X POST http://localhost:8089/api/v1/webhooks/polar \
  -H "Content-Type: application/json" \
  -H "webhook-id: msg_123" \
  -H "webhook-timestamp: 1706200000" \
  -H "webhook-signature: v1,base64signature..." \
  -d '{"type":"checkout.created","data":{"id":"checkout_123","status":"open"}}'

func PerformHandshake

func PerformHandshake(ctx context.Context, client apiv1connect.ApiServiceClient) (string, error)

PerformHandshake performs the device handshake to obtain a new token. It uses the provided client to make the call.

Types

type AppVersionLog added in v0.0.35

type AppVersionLog struct {
	ID                int64  `gorm:"primaryKey;autoIncrement" json:"id"`
	UserID            int64  `gorm:"not null;uniqueIndex:idx_user_device_version" json:"user_id"`
	DeviceFingerprint string `gorm:"not null;uniqueIndex:idx_user_device_version" json:"device_fingerprint"`
	Version           string `gorm:"not null;uniqueIndex:idx_user_device_version" json:"version"`
	Timestamp         int64  `gorm:"not null" json:"timestamp"`
}

func (*AppVersionLog) TableName added in v0.0.35

func (a *AppVersionLog) TableName() string

type CheckoutWebhookData

type CheckoutWebhookData struct {
	ID            string            `json:"id"`
	Status        string            `json:"status"`
	CustomerEmail string            `json:"customer_email,omitempty"`
	CustomerID    string            `json:"customer_id,omitempty"`
	ProductID     string            `json:"product_id,omitempty"`
	Metadata      map[string]string `json:"metadata,omitempty"`
}

CheckoutWebhookData represents the data for checkout.created/checkout.updated events.

type HandshakeNonce

type HandshakeNonce struct {
	Nonce     string `gorm:"not null;unique" json:"nonce"`
	CreatedAt int64  `gorm:"not null" json:"created_at"`
	ExpiresAt int64  `gorm:"not null" json:"expires_at"`
}

func (*HandshakeNonce) TableName

func (h *HandshakeNonce) TableName() string

type KeyManager

type KeyManager struct{}

KeyManager handles rotation. Keys are stored in env var: PASETO_KEYS="HEX_KEY_NEW,HEX_KEY_OLD"

func (KeyManager) GetActiveKey

func (km KeyManager) GetActiveKey() ([]byte, error)

func (KeyManager) GetAllKeys

func (km KeyManager) GetAllKeys() ([][]byte, error)

type LLMProxyUsage added in v0.0.24

type LLMProxyUsage struct {
	ID           int64  `gorm:"primaryKey;autoIncrement" json:"id"`
	UserID       int64  `gorm:"not null;index:idx_llm_user_time" json:"user_id"`
	CreatedAt    int64  `gorm:"not null;index:idx_llm_user_time" json:"created_at"`
	Provider     string `gorm:"not null;default:gemini" json:"provider"`
	InputTokens  int    `gorm:"not null;default:0" json:"input_tokens"`
	OutputTokens int    `gorm:"not null;default:0" json:"output_tokens"`
	TotalTokens  int    `gorm:"not null;default:0" json:"total_tokens"`
}

func (*LLMProxyUsage) TableName added in v0.0.24

func (l *LLMProxyUsage) TableName() string

type OrderWebhookData

type OrderWebhookData struct {
	ID            string            `json:"id"`
	CustomerID    string            `json:"customer_id"`
	ProductID     string            `json:"product_id,omitempty"`
	BillingReason string            `json:"billing_reason,omitempty"` // purchase, subscription_create, subscription_cycle, subscription_update
	Amount        int64             `json:"amount,omitempty"`
	Currency      string            `json:"currency,omitempty"`
	Status        string            `json:"status,omitempty"` // pending, paid
	Metadata      map[string]string `json:"metadata,omitempty"`
}

OrderWebhookData represents the data for order events.

type PolarWebhookEvent

type PolarWebhookEvent struct {
	Type string          `json:"type"`
	Data json.RawMessage `json:"data"`
}

PolarWebhookEvent represents a Polar.sh webhook event payload. Polar.sh follows the Standard Webhooks specification for signature verification.

Webhook Event Types:

  • checkout.created, checkout.updated
  • subscription.created, subscription.active, subscription.canceled, subscription.updated, subscription.revoked
  • order.created, order.paid, order.updated, order.refunded
  • customer.created, customer.updated, customer.deleted, customer.state_changed
  • benefit_grant.created, benefit_grant.updated, benefit_grant.revoked

Example payload for checkout.created:

{
  "type": "checkout.created",
  "data": {
    "id": "checkout_123",
    "status": "open",
    "customer_email": "user@example.com",
    "product_id": "prod_123",
    "metadata": {
      "user_id": "42"
    }
  }
}

Example payload for subscription.active:

{
  "type": "subscription.active",
  "data": {
    "id": "sub_123",
    "status": "active",
    "customer_id": "cust_123",
    "product_id": "prod_123",
    "current_period_start": "2025-01-01T00:00:00Z",
    "current_period_end": "2025-02-01T00:00:00Z"
  }
}

Example payload for order.created:

{
  "type": "order.created",
  "data": {
    "id": "order_123",
    "customer_id": "cust_123",
    "product_id": "prod_123",
    "billing_reason": "subscription_create",
    "amount": 999,
    "currency": "usd",
    "metadata": {
      "user_id": "42"
    }
  }
}

type ServiceImpl

type ServiceImpl struct {
	// contains filtered or unexported fields
}

func NewServiceImpl

func NewServiceImpl(gormDB *gorm.DB, productIDs map[apiv1.CheckoutProduct]string) (*ServiceImpl, error)

func (*ServiceImpl) CheckoutCustomerPortal added in v0.0.24

func (*ServiceImpl) DeviceHandshake

DeviceHandshake performs the initial authentication flow for a device: This is a public endpoint and doesn't use the standard AuthInterceptor.

1. Verify HMAC Signature (App Attestation) manually since this is a public endpoint. 2. Find or create a shadow user based on the device fingerprint. 3. Mint a PASETO session token for the user. 4. Return the session token.

type SigningInterceptor

type SigningInterceptor struct {
}

SigningInterceptor is a client-side interceptor that handles authentication by attaching tokens to requests and refreshing them when they expire.

func NewSigningInterceptor

func NewSigningInterceptor() *SigningInterceptor

NewSigningInterceptor creates a new signing interceptor.

func (*SigningInterceptor) WrapStreamingClient

WrapStreamingClient implements connect.Interceptor for streaming client RPCs.

func (*SigningInterceptor) WrapStreamingHandler

WrapStreamingHandler implements connect.Interceptor (no-op for client-side).

func (*SigningInterceptor) WrapUnary

WrapUnary implements connect.Interceptor for unary RPCs.

type SigningRoundTripper added in v0.0.24

type SigningRoundTripper struct {
	Base http.RoundTripper
}

SigningRoundTripper is an http.RoundTripper that attaches authentication tokens to outgoing requests.

func NewSigningRoundTripper added in v0.0.24

func NewSigningRoundTripper(base http.RoundTripper) *SigningRoundTripper

NewSigningRoundTripper creates a new signing round tripper.

func (*SigningRoundTripper) RoundTrip added in v0.0.24

func (s *SigningRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

type SubscriptionWebhookData

type SubscriptionWebhookData struct {
	ID                 string            `json:"id"`
	Status             string            `json:"status"`
	CustomerID         string            `json:"customer_id"`
	ProductID          string            `json:"product_id,omitempty"`
	CurrentPeriodStart string            `json:"current_period_start,omitempty"`
	CurrentPeriodEnd   string            `json:"current_period_end,omitempty"`
	CancelAtPeriodEnd  bool              `json:"cancel_at_period_end,omitempty"`
	TrialStart         *time.Time        `json:"trial_start,omitempty"`
	TrialEnd           *time.Time        `json:"trial_end,omitempty"`
	Metadata           map[string]string `json:"metadata,omitempty"`
}

SubscriptionWebhookData represents the data for subscription events.

type Tier

type Tier string
const (
	TierFree  Tier = "free"
	TierTrial Tier = "trial"
	TierPlus  Tier = "plus"
	TierPro   Tier = "pro"
)

type User

type User struct {
	ID            int64        `gorm:"primaryKey;autoIncrement" json:"id"`
	Role          string       `gorm:"default:anonymous;not null" json:"role"`
	CreatedAt     int64        `gorm:"not null" json:"created_at"`
	Devices       []UserDevice `gorm:"foreignKey:UserID" json:"devices"`
	Tier          string       `gorm:"default:trial;not null" json:"tier"`
	TierChangedAt int64        `gorm:"not null" json:"tier_changed_at"`

	// polar
	PolarCustomerID     string `gorm:"not null;unique" json:"polar_customer_id"`
	PolarPastDue        bool   `gorm:"default:false;not null" json:"polar_past_due"`
	PolarSubscriptionID string `gorm:"not null;unique" json:"polar_subscription_id"`
}

func (*User) TableName

func (u *User) TableName() string

type UserClaims

type UserClaims struct {
	UserID    int64     `json:"sub"`
	Role      string    `json:"role"` // "anonymous" or "pro"
	ExpiresAt time.Time `json:"exp"`
	Tier      string    `json:"tier"`
}

UserClaims represents the data inside the encrypted token

func GetClaims

func GetClaims(ctx context.Context) (*UserClaims, error)

func GetUser

func GetUser(ctx context.Context) (*UserClaims, bool)

GetUser extracts user data from context in your API handlers

func ValidateToken

func ValidateToken(tokenStr string) (*UserClaims, error)

ValidateToken decrypts the token trying all available keys

func (*UserClaims) Valid

func (c *UserClaims) Valid() error

Valid checks if token is expired

type UserDevice

type UserDevice struct {
	ID          int64  `gorm:"primaryKey;autoIncrement" json:"id"`
	UserID      int64  `gorm:"not null" json:"user_id"`
	Fingerprint string `gorm:"not null;unique" json:"fingerprint"`
	CreatedAt   int64  `gorm:"not null" json:"created_at"`
	User        User   `gorm:"foreignKey:UserID" json:"user"`
}

func (*UserDevice) TableName

func (u *UserDevice) TableName() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL