Documentation
¶
Overview ¶
Package webapi hosts the /api/* and /oauth/* browser-facing HTTP surface. Runner-facing endpoints live in internal/api.
Index ¶
- Constants
- func CSRF(cfg CSRFConfig) func(http.Handler) http.Handler
- func ClearCSRFCookie(w http.ResponseWriter)
- func NewCSRFToken() (string, error)
- func NewTestSessionCookie(masterKey []byte, login, role string) (*http.Cookie, error)
- func RegisterRoutes(mux *http.ServeMux, deps Deps)
- func RequireRole(masterKey []byte, role string, next http.Handler) http.Handler
- func RequireSession(masterKey []byte, next http.Handler) http.Handler
- func ResolveCopilotToken(ctx context.Context, store server.SecretStore, prefix string, ...) (string, time.Time, error)
- func SetCSRFCookie(w http.ResponseWriter, token string, maxAge int, host string)
- func SignOAuthState(key []byte, ttl time.Duration) (string, error)
- func SignSession(claims SessionClaims, key []byte, ttl time.Duration) (string, error)
- type CSRFConfig
- type CopilotTokenRefsJSON
- type Deps
- type RateLimiter
- type RateLimiterConfig
- type RepoSyncer
- type SessionClaims
- type UIOverrides
Constants ¶
const CSRFCookieName = "cf_csrf"
CSRFCookieName is the cookie that carries the per-session CSRF token. It is non-HttpOnly so the SPA can read it via document.cookie and echo it in X-CSRF-Token. The cookie's presence on the cronfoundry origin is the security primitive — an attacker on a foreign origin cannot read it.
const CSRFHeaderName = "X-CSRF-Token"
CSRFHeaderName is the header the SPA must set on every state-changing request. The middleware compares its value to the cookie with a constant-time compare.
const CopilotClientID = "Iv1.b507a08c87ecfe98"
CopilotClientID is the public OAuth client_id of the GitHub Copilot platform — used for device-flow against GitHub's /login/device/code and /login/oauth/access_token endpoints to mint a Copilot seat token. This value is published by GitHub for any consumer doing Copilot OAuth (including the official VS Code and JetBrains Copilot extensions).
It is NOT the operator's CronFoundry GitHub App client ID; that App authenticates webhooks and per-installation API calls and has nothing to do with the user's Copilot seat.
Variables ¶
This section is empty.
Functions ¶
func CSRF ¶
func CSRF(cfg CSRFConfig) func(http.Handler) http.Handler
CSRF returns a middleware that enforces double-submit cookie + Origin check on all non-safe HTTP methods. GET, HEAD, and OPTIONS pass through unchanged (these are the IETF "safe methods"); every other method — including POST, PATCH, PUT, DELETE, and any unknown verb — must present a matching cf_csrf cookie and X-CSRF-Token header.
func ClearCSRFCookie ¶
func ClearCSRFCookie(w http.ResponseWriter)
ClearCSRFCookie deletes the cf_csrf cookie. Called from logout.
func NewCSRFToken ¶
NewCSRFToken returns a 32-byte random token, base64url-encoded without padding (43 chars). Suitable for use as a per-session CSRF token.
func NewTestSessionCookie ¶
NewTestSessionCookie creates a signed session cookie for use in handler tests.
func RegisterRoutes ¶
RegisterRoutes registers /oauth/*, /api/*, and /* (SPA catch-all) on mux.
func RequireRole ¶
RequireRole wraps a handler requiring a valid session with the given role. "admin" role may access "viewer" routes; "viewer" may not access "admin" routes.
func RequireSession ¶
RequireSession is middleware that validates the cf_session cookie. Attaches SessionClaims to the request context on success; returns 401 otherwise.
func ResolveCopilotToken ¶
func ResolveCopilotToken(ctx context.Context, store server.SecretStore, prefix string, githubOverrideURL *string) (string, time.Time, error)
ResolveCopilotToken reads the access token for the given prefix from the secret store. If the token is within 60 seconds of expiry, it refreshes using the stored refresh token and writes the new values back.
githubOverrideURL overrides the GitHub token endpoint for tests; pass nil to use the real GitHub endpoint.
func SetCSRFCookie ¶
func SetCSRFCookie(w http.ResponseWriter, token string, maxAge int, host string)
SetCSRFCookie writes the cf_csrf cookie. Called from the OAuth callback alongside SetCookie for cf_session. HttpOnly is intentionally false.
func SignOAuthState ¶
SignOAuthState generates a signed random state token for CSRF protection.
func SignSession ¶
SignSession encodes claims as a signed cookie value:
base64url(JSON) + "." + base64url(HMAC-SHA256(payload, key))
Types ¶
type CSRFConfig ¶
type CSRFConfig struct {
// AllowedOrigin is the scheme+host of the trusted public origin
// (e.g. "https://cronfoundry.example.com"). When empty, the
// Origin/Referer check is skipped — intended for local dev only.
AllowedOrigin string
}
CSRFConfig configures the CSRF middleware.
type CopilotTokenRefsJSON ¶
type CopilotTokenRefsJSON struct {
Prefix string `json:"prefix"`
}
CopilotTokenRefsJSON is stored on the schedule row and identifies which KV secrets hold the token pair for a copilot-enterprise schedule.
type Deps ¶
type Deps struct {
MasterKey []byte
OAuthClientID string
OAuthClientSecret string
AdminLogins []string
ViewerLogins []string
// GitHubAPIBase overrides the GitHub API base URL in tests. Empty = real GitHub.
GitHubAPIBase string
// Queries provides DB access for /api/* handlers.
Queries *dbgen.Queries
// Secrets provides secret store access for /api/secrets handlers.
Secrets server.SecretStore
// APIBaseURL is the base URL for the internal API (used by run-now).
APIBaseURL string
// WebhookSecret is the shared HMAC secret registered with the GitHub App.
// When empty, POST /webhook/github responds 503 Service Unavailable.
WebhookSecret []byte
// Syncer triggers a one-off repo sync. Injected from cmd/cronfoundry/serve.go
// as a thin wrapper around sync.Poller.SyncOne.
Syncer RepoSyncer
// RateLimit configures per-IP rate limiting on public routes.
RateLimit RateLimiterConfig
// PublicBaseURL is the externally-reachable base URL of the service
// (scheme+host, e.g. "https://cronfoundry.example.com"). Used by the CSRF
// middleware as the Origin/Referer allowlist. Empty disables the Origin
// check (dev mode); the cookie+header double-submit check still runs.
PublicBaseURL string
}
Deps holds everything webapi handlers need.
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter holds per-group token-bucket state plus an SSE concurrency counter. Construct with NewRateLimiter.
func NewRateLimiter ¶
func NewRateLimiter(cfg RateLimiterConfig) (*RateLimiter, error)
NewRateLimiter returns a RateLimiter. LRUSize defaults to 4096 if < 1.
type RateLimiterConfig ¶
type RateLimiterConfig struct {
TrustProxy bool
Disabled bool
APIRPM int
OAuthRPM int
WebhookRPM int
SSEMaxConcurrent int
LRUSize int
}
RateLimiterConfig holds the operator-tunable rate-limit knobs. Zero RPM for a group disables that group; Disabled=true disables the entire middleware (kill switch).
type RepoSyncer ¶
RepoSyncer resolves a repo_connection by ID and triggers a single sync pass. The concrete implementation in serve.go wraps sync.Poller.SyncOne.
type SessionClaims ¶
type SessionClaims struct {
Login string `json:"login"`
Role string `json:"role"`
Exp int64 `json:"exp"` // Unix seconds
}
SessionClaims is the payload embedded in the cf_session cookie.
func SessionClaimsFromContext ¶
func SessionClaimsFromContext(ctx context.Context) SessionClaims
SessionClaimsFromContext returns the claims attached by RequireSession. Returns zero value if the middleware was not applied.
func VerifySession ¶
func VerifySession(cookie string, key []byte) (SessionClaims, error)
VerifySession parses and validates a signed cookie value produced by SignSession.
type UIOverrides ¶
type UIOverrides struct {
Cron *string `json:"cron,omitempty"`
Timezone *string `json:"timezone,omitempty"`
TimeoutSec *int32 `json:"timeout_sec,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
}
UIOverrides is the subset of schedule fields editable via the UI. Pointer fields: nil means "not overridden".