Documentation
¶
Index ¶
- Constants
- func MintToken(user User, role string) (string, error)
- func NewAuthInterceptor(gormDB *gorm.DB) connect.Interceptor
- func NewClient(baseURL string, interceptors ...connect.Interceptor) apiv1connect.ApiServiceClient
- func NewHTTP2Client() *http.Client
- func NewHTTP2ClientWithTimeout(timeout time.Duration) *http.Client
- func NewPolarWebhookHandler(s *ServiceImpl) http.HandlerFunc
- func PerformHandshake(ctx context.Context, client apiv1connect.ApiServiceClient) (string, error)
- type AppVersionLog
- type CheckoutWebhookData
- type HandshakeNonce
- type KeyManager
- type LLMProxyUsage
- type OrderWebhookData
- type PolarWebhookEvent
- type ServiceImpl
- func (s *ServiceImpl) CheckoutCustomerPortal(ctx context.Context, req *connect.Request[apiv1.CheckoutCustomerPortalRequest]) (*connect.Response[apiv1.CheckoutCustomerPortalResponse], error)
- func (s *ServiceImpl) CheckoutGetLink(ctx context.Context, req *connect.Request[apiv1.CheckoutGetLinkRequest]) (*connect.Response[apiv1.CheckoutGetLinkResponse], error)
- func (s *ServiceImpl) DeviceHandshake(ctx context.Context, req *connect.Request[apiv1.DeviceHandshakeRequest]) (*connect.Response[apiv1.DeviceHandshakeResponse], error)
- type SigningInterceptor
- func (i *SigningInterceptor) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc
- func (i *SigningInterceptor) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc
- func (i *SigningInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc
- type SigningRoundTripper
- type SubscriptionWebhookData
- type Tier
- type User
- type UserClaims
- type UserDevice
Constants ¶
const ( KeychainService = "focusd-engine" KeychainUser = "auth-token" )
Variables ¶
This section is empty.
Functions ¶
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 ¶
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 ¶
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 (s *ServiceImpl) CheckoutCustomerPortal(ctx context.Context, req *connect.Request[apiv1.CheckoutCustomerPortalRequest]) (*connect.Response[apiv1.CheckoutCustomerPortalResponse], error)
func (*ServiceImpl) CheckoutGetLink ¶
func (s *ServiceImpl) CheckoutGetLink(ctx context.Context, req *connect.Request[apiv1.CheckoutGetLinkRequest]) (*connect.Response[apiv1.CheckoutGetLinkResponse], error)
func (*ServiceImpl) DeviceHandshake ¶
func (s *ServiceImpl) DeviceHandshake(ctx context.Context, req *connect.Request[apiv1.DeviceHandshakeRequest]) (*connect.Response[apiv1.DeviceHandshakeResponse], error)
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 ¶
func (i *SigningInterceptor) WrapStreamingClient(next connect.StreamingClientFunc) connect.StreamingClientFunc
WrapStreamingClient implements connect.Interceptor for streaming client RPCs.
func (*SigningInterceptor) WrapStreamingHandler ¶
func (i *SigningInterceptor) WrapStreamingHandler(next connect.StreamingHandlerFunc) connect.StreamingHandlerFunc
WrapStreamingHandler implements connect.Interceptor (no-op for client-side).
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.
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 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"`
}
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 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
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