server

package
v0.0.0-...-c6922af Latest Latest
Warning

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

Go to latest
Published: Mar 2, 2026 License: MIT Imports: 43 Imported by: 0

Documentation

Overview

Package server provides HTTP handlers for the tinkerdown server.

Webhooks

Webhooks allow external services to trigger actions via HTTP POST requests. They are useful for integrating with CI/CD systems, monitoring tools, and other external services.

## Endpoint Format

POST /webhook/{name}

## Request Format

The request body should be JSON with parameters for the action:

{
  "params": {
    "key": "value"
  }
}

Or directly as a JSON object:

{
  "key": "value"
}

## Authentication

Webhooks support three authentication methods:

1. Simple Secret (via header):

X-Webhook-Secret: your-secret-here

2. Simple Secret (via query parameter):

POST /webhook/name?secret=your-secret-here

3. HMAC Signature (recommended for production):

X-Webhook-Signature: sha256=<hex-encoded-hmac>

The signature is computed as HMAC-SHA256 of the request body using the configured signature_secret.

## Replay Attack Prevention

Enable timestamp validation to prevent replay attacks:

X-Webhook-Timestamp: <unix-timestamp>

Requests older than timestamp_tolerance (default: 5 minutes) are rejected.

## Rate Limiting

Webhooks are rate-limited to 10 requests/second with a burst of 20 by default.

## Concurrency Control

A maximum of 10 actions can execute concurrently. Additional requests wait up to 5 seconds for a slot before returning 503 Service Unavailable.

## Example: GitHub Webhook

# tinkerdown.yaml
actions:
  sync-repo:
    kind: exec
    cmd: git pull

webhooks:
  github:
    action: sync-repo
    signature_secret: ${GITHUB_WEBHOOK_SECRET}

## Example: cURL Request with HMAC

BODY='{"params":{"branch":"main"}}'
SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
curl -X POST http://localhost:8080/webhook/deploy \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: sha256=$SIG" \
  -H "X-Webhook-Timestamp: $(date +%s)" \
  -d "$BODY"

Index

Constants

View Source
const ExpressionsBlockID = "__expressions__"

ExpressionsBlockID is the special block ID used for routing expression update messages. This must match the value expected by the client-side MessageRouter.

Variables

This section is empty.

Functions

func AuthMiddleware

func AuthMiddleware(authCfg *config.AuthConfig) func(http.Handler) http.Handler

AuthMiddleware validates API key authentication with support for multiple keys. If no keys are configured, authentication is disabled and all requests pass through.

func CORSMiddleware

func CORSMiddleware(origins []string, authHeaderName string) func(http.Handler) http.Handler

CORSMiddleware adds CORS headers to responses. If origins is empty or nil, CORS headers are not added. authHeaderName is included in Access-Control-Allow-Headers when non-empty.

func HasPermission

func HasPermission(r *http.Request, perm config.Permission) bool

HasPermission checks if the request has a specific permission from auth context.

func MethodPermissionMiddleware

func MethodPermissionMiddleware() func(http.Handler) http.Handler

MethodPermissionMiddleware checks that the authenticated key has permission for the requested HTTP method. Must be used after AuthMiddleware.

func RateLimitMiddleware

func RateLimitMiddleware(ctx context.Context, rps float64, burst int, maxIPs int) (func(http.Handler) http.Handler, <-chan struct{})

RateLimitMiddleware limits requests using a token bucket algorithm with per-IP tracking. rps is the rate limit in requests per second, burst is the maximum burst size, and maxIPs is the maximum number of unique IPs to track (LRU eviction when full). maxIPs <= 0 defaults to 10000.

For maxIPs >= defaultNumShards (or maxIPs == 0, which defaults to 10000), a sharded implementation is used to reduce lock contention under parallel load. For 0 < maxIPs < defaultNumShards, a single-mutex implementation is used to avoid zero-capacity shards.

The cleanup goroutine starts immediately when this function is called. Callers must cancel ctx and then receive from the returned channel to guarantee the goroutine has exited.

func SecurityHeadersMiddleware

func SecurityHeadersMiddleware() func(http.Handler) http.Handler

SecurityHeadersMiddleware adds security headers to all responses.

func WithCompression

func WithCompression(h http.Handler) http.Handler

WithCompression wraps an http.Handler with compression middleware

Types

type APIHandler

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

APIHandler handles REST API requests for sources.

func NewAPIHandler

func NewAPIHandler(cfg *config.Config, rootDir string) *APIHandler

NewAPIHandler creates a new API handler.

func (*APIHandler) Close

func (h *APIHandler) Close() error

Close releases all cached sources.

func (*APIHandler) ServeHTTP

func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP handles API requests. Expected path format: /api/sources/{name}[/{itemID}]

type BlockInstance

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

BlockInstance represents a running LiveTemplate instance for an interactive block.

type CacheMeta

type CacheMeta struct {
	Cached     bool   `json:"cached"`
	Age        string `json:"age,omitempty"`
	ExpiresIn  string `json:"expires_in,omitempty"`
	Stale      bool   `json:"stale"`
	Refreshing bool   `json:"refreshing"`
}

CacheMeta contains cache state for cached source blocks

type ExecMeta

type ExecMeta struct {
	Status   string `json:"status"`
	Duration int64  `json:"duration,omitempty"`
	Output   string `json:"output,omitempty"`
	Stderr   string `json:"stderr,omitempty"`
	Command  string `json:"command,omitempty"`
}

ExecMeta contains execution state for exec source blocks

type MessageEnvelope

type MessageEnvelope struct {
	BlockID   string          `json:"blockID"`
	Action    string          `json:"action"`
	Data      json.RawMessage `json:"data"`
	ExecMeta  *ExecMeta       `json:"execMeta,omitempty"`  // Optional exec source metadata
	CacheMeta *CacheMeta      `json:"cacheMeta,omitempty"` // Optional cache metadata
}

MessageEnvelope represents a multiplexed WebSocket message.

type PlaygroundHandler

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

PlaygroundHandler handles playground-related requests.

func NewPlaygroundHandler

func NewPlaygroundHandler(s *Server) *PlaygroundHandler

NewPlaygroundHandler creates a new playground handler.

func (*PlaygroundHandler) HandlePreview

func (h *PlaygroundHandler) HandlePreview(w http.ResponseWriter, r *http.Request)

HandlePreview handles GET /playground/preview/{sessionId}.

func (*PlaygroundHandler) HandlePreviewWS

func (h *PlaygroundHandler) HandlePreviewWS(w http.ResponseWriter, r *http.Request)

HandlePreviewWS handles WebSocket connections for playground previews.

func (*PlaygroundHandler) HandleRender

func (h *PlaygroundHandler) HandleRender(w http.ResponseWriter, r *http.Request)

HandleRender handles POST /playground/render.

func (*PlaygroundHandler) ServePlaygroundPage

func (h *PlaygroundHandler) ServePlaygroundPage(w http.ResponseWriter, r *http.Request)

ServePlaygroundPage serves the playground HTML page.

type PlaygroundSession

type PlaygroundSession struct {
	ID        string
	Page      *tinkerdown.Page
	Markdown  string
	CreatedAt time.Time
}

PlaygroundSession represents an active playground session.

type RenderRequest

type RenderRequest struct {
	Markdown string `json:"markdown"`
}

RenderRequest is the JSON request body for /playground/render.

type RenderResponse

type RenderResponse struct {
	SessionID string `json:"sessionId"`
	Error     string `json:"error,omitempty"`
}

RenderResponse is the JSON response for /playground/render.

type Route

type Route struct {
	Pattern  string           // URL pattern (e.g., "/counter")
	FilePath string           // Relative file path (e.g., "counter.md")
	Page     *tinkerdown.Page // Parsed page
}

Route represents a discovered page route.

type Server

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

Server is the tinkerdown development server.

func New

func New(rootDir string) *Server

New creates a new server for the given root directory.

func NewWithConfig

func NewWithConfig(rootDir string, cfg *config.Config) *Server

NewWithConfig creates a new server with a specific configuration.

func (*Server) AddJob

func (s *Server) AddJob(job *schedule.Job)

AddJob adds a job to the schedule runner (exposed for external registration).

func (*Server) BroadcastReload

func (s *Server) BroadcastReload(filePath string)

BroadcastReload sends a reload message to all connected WebSocket clients.

func (*Server) Discover

func (s *Server) Discover() error

Discover scans the directory for .md files and creates routes.

func (*Server) EnableWatch

func (s *Server) EnableWatch(debug bool) error

EnableWatch enables file watching for live reload.

func (*Server) GetScheduledJobCount

func (s *Server) GetScheduledJobCount() int

GetScheduledJobCount returns the number of scheduled jobs.

func (*Server) MarkSourceWrite

func (s *Server) MarkSourceWrite(filePath string)

MarkSourceWrite records that a source action just wrote to this file. Used by the watcher to distinguish source writes (e.g., checkbox toggle) from external edits that require a full page reload.

func (*Server) RefreshSourcesForFile

func (s *Server) RefreshSourcesForFile(filePath string)

RefreshSourcesForFile triggers a refresh on all sources that use the given file.

func (*Server) RegisterConnection

func (s *Server) RegisterConnection(conn *websocket.Conn, handler *WebSocketHandler)

RegisterConnection adds a WebSocket connection to the tracked connections.

func (*Server) Routes

func (s *Server) Routes() []*Route

Routes returns the discovered routes.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

func (*Server) StartSchedules

func (s *Server) StartSchedules(ctx context.Context) error

StartSchedules starts the schedule runner for timed job execution. This should be called after Discover() to ensure all page schedules are registered.

func (*Server) StopRateLimiter

func (s *Server) StopRateLimiter()

StopRateLimiter cancels the rate limiter's background cleanup goroutine and blocks until it exits. If a cleanup sweep is in progress, this may block briefly until the sweep finishes and the goroutine observes the cancellation.

func (*Server) StopSchedules

func (s *Server) StopSchedules() error

StopSchedules stops the schedule runner.

func (*Server) StopWatch

func (s *Server) StopWatch() error

StopWatch stops the file watcher if it's running.

func (*Server) UnregisterConnection

func (s *Server) UnregisterConnection(conn *websocket.Conn)

UnregisterConnection removes a WebSocket connection from tracked connections.

type Watcher

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

Watcher watches for file changes and triggers reload.

func NewWatcher

func NewWatcher(rootDir string, onReload func(string) error, debug bool) (*Watcher, error)

NewWatcher creates a new file watcher for the given directory.

func (*Watcher) Start

func (w *Watcher) Start()

Start begins watching for file changes.

func (*Watcher) Stop

func (w *Watcher) Stop() error

Stop stops the watcher.

type WebSocketHandler

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

WebSocketHandler handles WebSocket connections for interactive blocks.

func NewWebSocketHandler

func NewWebSocketHandler(page *tinkerdown.Page, server *Server, debug bool, rootDir string, cfg *config.Config) *WebSocketHandler

NewWebSocketHandler creates a new WebSocket handler for a page.

func (*WebSocketHandler) Close

func (h *WebSocketHandler) Close()

Close cleans up all resources

func (*WebSocketHandler) RefreshSourcesForFile

func (h *WebSocketHandler) RefreshSourcesForFile(filePath string)

RefreshSourcesForFile refreshes all sources that use the given file. This is called by the server when a markdown source file changes externally.

func (*WebSocketHandler) ServeHTTP

func (h *WebSocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP handles WebSocket upgrade and message routing.

func (*WebSocketHandler) TracksSourceFile

func (h *WebSocketHandler) TracksSourceFile(filePath string) bool

TracksSourceFile returns true if any source in this handler's sourceFiles map matches the given file path. Used to detect same-file sources (auto-tasks).

type WebhookAuditLog

type WebhookAuditLog struct {
	Timestamp   time.Time              `json:"timestamp"`
	WebhookName string                 `json:"webhook_name"`
	ActionName  string                 `json:"action_name"`
	RemoteAddr  string                 `json:"remote_addr"`
	UserAgent   string                 `json:"user_agent"`
	Success     bool                   `json:"success"`
	Error       string                 `json:"error,omitempty"`
	Params      map[string]interface{} `json:"params,omitempty"`
}

WebhookAuditLog represents an audit log entry for webhook invocations.

type WebhookHandler

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

WebhookHandler handles incoming webhook HTTP requests. Webhooks allow external services to trigger actions via POST requests to /webhook/{name} endpoints.

func NewWebhookHandler

func NewWebhookHandler(cfg *config.Config, rootDir string, actionHandler func(string, map[string]interface{}) error) *WebhookHandler

NewWebhookHandler creates a new webhook handler.

func (*WebhookHandler) ServeHTTP

func (h *WebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP handles webhook requests. Expected path format: /webhook/{name}

type WebhookRequest

type WebhookRequest struct {
	Params map[string]interface{} `json:"params,omitempty"`
}

WebhookRequest represents the parsed webhook request body.

type WebhookResponse

type WebhookResponse struct {
	Success bool   `json:"success"`
	Message string `json:"message,omitempty"`
	Error   string `json:"error,omitempty"`
}

WebhookResponse represents the JSON response from a webhook call.

Jump to

Keyboard shortcuts

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