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
- func AuthMiddleware(authCfg *config.AuthConfig) func(http.Handler) http.Handler
- func CORSMiddleware(origins []string, authHeaderName string) func(http.Handler) http.Handler
- func HasPermission(r *http.Request, perm config.Permission) bool
- func MethodPermissionMiddleware() func(http.Handler) http.Handler
- func RateLimitMiddleware(ctx context.Context, rps float64, burst int, maxIPs int) (func(http.Handler) http.Handler, <-chan struct{})
- func SecurityHeadersMiddleware() func(http.Handler) http.Handler
- func WithCompression(h http.Handler) http.Handler
- type APIHandler
- type BlockInstance
- type CacheMeta
- type ExecMeta
- type MessageEnvelope
- type PlaygroundHandler
- func (h *PlaygroundHandler) HandlePreview(w http.ResponseWriter, r *http.Request)
- func (h *PlaygroundHandler) HandlePreviewWS(w http.ResponseWriter, r *http.Request)
- func (h *PlaygroundHandler) HandleRender(w http.ResponseWriter, r *http.Request)
- func (h *PlaygroundHandler) ServePlaygroundPage(w http.ResponseWriter, r *http.Request)
- type PlaygroundSession
- type RenderRequest
- type RenderResponse
- type Route
- type Server
- func (s *Server) AddJob(job *schedule.Job)
- func (s *Server) BroadcastReload(filePath string)
- func (s *Server) Discover() error
- func (s *Server) EnableWatch(debug bool) error
- func (s *Server) GetScheduledJobCount() int
- func (s *Server) MarkSourceWrite(filePath string)
- func (s *Server) RefreshSourcesForFile(filePath string)
- func (s *Server) RegisterConnection(conn *websocket.Conn, handler *WebSocketHandler)
- func (s *Server) Routes() []*Route
- func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Server) StartSchedules(ctx context.Context) error
- func (s *Server) StopRateLimiter()
- func (s *Server) StopSchedules() error
- func (s *Server) StopWatch() error
- func (s *Server) UnregisterConnection(conn *websocket.Conn)
- type Watcher
- type WebSocketHandler
- type WebhookAuditLog
- type WebhookHandler
- type WebhookRequest
- type WebhookResponse
Constants ¶
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 ¶
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 ¶
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 ¶
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 ¶
SecurityHeadersMiddleware adds security headers to all responses.
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) 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 NewWithConfig ¶
NewWithConfig creates a new server with a specific configuration.
func (*Server) AddJob ¶
AddJob adds a job to the schedule runner (exposed for external registration).
func (*Server) BroadcastReload ¶
BroadcastReload sends a reload message to all connected WebSocket clients.
func (*Server) EnableWatch ¶
EnableWatch enables file watching for live reload.
func (*Server) GetScheduledJobCount ¶
GetScheduledJobCount returns the number of scheduled jobs.
func (*Server) MarkSourceWrite ¶
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 ¶
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) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler.
func (*Server) StartSchedules ¶
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 ¶
StopSchedules stops the schedule runner.
func (*Server) UnregisterConnection ¶
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 ¶
NewWatcher creates a new file watcher for the given directory.
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) 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.