Documentation
¶
Overview ¶
Package borgo is the go side of the borgo framework: a route registry and a server bootstrap. API files register their handlers in init() via Handle, and main calls Serve. The core imposes no database and no dependencies.
Index ¶
- Variables
- func Authed(next http.HandlerFunc) http.HandlerFunc
- func Bind[T any](r *http.Request) (T, error)
- func BindError(w http.ResponseWriter, err error)
- func BindMax[T any](r *http.Request, limit int64) (T, error)
- func Cache(w http.ResponseWriter, maxAge time.Duration, ...)
- func ClearSession(w http.ResponseWriter)
- func GetSession[T any](r *http.Request) (T, bool)
- func Handle(pattern string, h http.HandlerFunc)
- func JSON[T any](w http.ResponseWriter, status int, v T)
- func NoCache(w http.ResponseWriter)
- func Push(topic, event string, data any) error
- func PushT[T any](topic, event string, data T) error
- func Serve()
- func SetSession(w http.ResponseWriter, v any, maxAge time.Duration) error
- func WriteJSON(w http.ResponseWriter, status int, v any)
- type Auth
- type Credentials
- type PasswordHasher
- type SSEHub
- type SSEStream
Constants ¶
This section is empty.
Variables ¶
var ErrNoSessionSecret = errors.New("borgo: SESSION_SECRET must be set to use sessions (any long random string)")
ErrNoSessionSecret is returned by SetSession when SESSION_SECRET is unset. It is an error rather than a panic because the app is already serving: a login that answers 500 is recoverable, a panicking handler is not.
var ErrUserExists = errors.New("user already exists")
ErrUserExists signals from Auth.Register that the username is taken; the RegisterHandler answers it with 409 instead of 500.
Functions ¶
func Authed ¶ added in v0.11.0
func Authed(next http.HandlerFunc) http.HandlerFunc
Authed guards an api route: without a valid session the request is answered 401 as JSON and the handler never runs. borgogen sees through the wrapper, so the route keeps its generated types. Pages guard themselves in their loader instead - see docs/auth-and-sessions.md.
func Bind ¶
Bind decodes the request body as JSON into T, reading at most 1 MB - use BindMax for routes that legitimately take more. Its type parameter is visible to static analysis: borgogen reads T to type the route's request body for the TypeScript api client. On error, respond with BindError to get the right status (413 for an oversized body).
func BindError ¶ added in v0.11.0
func BindError(w http.ResponseWriter, err error)
BindError answers a Bind error: 413 when the body exceeded the limit, 415 for a non-JSON content type, 400 for anything else, as JSON.
func BindMax ¶ added in v0.11.0
BindMax is Bind with an explicit body size limit in bytes; limit <= 0 disables the cap.
func Cache ¶
Cache marks the response publicly cacheable for maxAge. An optional staleWhileRevalidate window lets proxies serve stale content while they refresh in the background. A response that already carries Set-Cookie is marked private instead, so shared caches never store it - which means calling Cache before SetSession skips that check: set cookies first.
func ClearSession ¶
func ClearSession(w http.ResponseWriter)
ClearSession deletes the session cookie.
func GetSession ¶
GetSession verifies the session cookie's signature and expiry and decodes its payload into T. The second return is false for a missing, tampered or expired session.
func Handle ¶
func Handle(pattern string, h http.HandlerFunc)
Handle registers a handler under a net/http method pattern, e.g. "GET /api/tasks" or "GET /api/tasks/{id}".
func JSON ¶
func JSON[T any](w http.ResponseWriter, status int, v T)
JSON writes v as a JSON response with the given status code. Unlike WriteJSON its type parameter is visible to static analysis: borgogen reads T from every JSON call in a handler to type the route for TypeScript.
func NoCache ¶
func NoCache(w http.ResponseWriter)
NoCache marks the response as never cacheable - right for anything personalized or session-dependent.
func Push ¶
Push publishes an event to every browser subscribed to a websocket topic on the front server (see the subscribe helper in the borgo npm package). The front server is assumed on localhost; set FRONT_URL when it is not, and BORGO_PUSH_KEY on both sides when pushing across hosts.
func PushT ¶ added in v0.11.0
PushT is Push with the payload type visible to static analysis. Call it with literal topic and event strings and borgogen records the payload type in the generated event map, typing the browser's subscribe callback for that topic (mirroring how borgo.JSON[T] types a route's response).
func Serve ¶
func Serve()
Serve mounts every registered route and listens on API_PORT (default 3501). It also answers GET /healthz, unless a registered route claims it.
func SetSession ¶
SetSession stores v, JSON-encoded and HMAC-signed with SESSION_SECRET, in an http-only cookie. The expiry is signed too, so a client cannot extend it. Set SESSION_SECURE=1 to add the Secure attribute behind https. A maxAge of zero or less writes an already-expired session.
Types ¶
type Auth ¶ added in v0.11.0
type Auth[U any] struct { // Lookup returns the user and its stored password hash for a username. // Any error is answered as invalid credentials, so a missing user is // indistinguishable from a wrong password. Lookup func(ctx context.Context, username string) (U, string, error) // Register creates a user from a username and an already-hashed password. // Optional: without it RegisterHandler answers 404. Return ErrUserExists // for a taken username. Register func(ctx context.Context, username, hash string) (U, error) // Principal maps the user to what the session stores. Optional: the // default stores the user itself. Keep it minimal - it rides in a cookie. Principal func(u U) any // MaxAge is the session lifetime, default 7 days. MaxAge time.Duration // Hasher verifies (and, on register, creates) password hashes. // Default: DefaultHasher. Hasher PasswordHasher // contains filtered or unexported fields }
Auth wires an app-supplied user provider to ready-made login, logout and register handlers over the signed-cookie session. Mechanics, not policy: borgo imposes no database and no user schema - Lookup and Register are yours, the session stores whatever principal you choose.
func (*Auth[U]) LoginHandler ¶ added in v0.11.0
func (a *Auth[U]) LoginHandler(w http.ResponseWriter, r *http.Request)
LoginHandler verifies the posted {username, password} against Lookup and starts a session with the principal, responding with it as JSON. Under more parallel attempts than the box can hash it answers 503 with Retry-After.
func (*Auth[U]) LogoutHandler ¶ added in v0.11.0
func (a *Auth[U]) LogoutHandler(w http.ResponseWriter, r *http.Request)
LogoutHandler clears the session cookie.
func (*Auth[U]) RegisterHandler ¶ added in v0.11.0
func (a *Auth[U]) RegisterHandler(w http.ResponseWriter, r *http.Request)
RegisterHandler hashes the posted password, creates the user through Register and starts a session, responding 201 with the principal. A taken username is a 409, which tells the caller the name exists: pair it with a generic message in the ui if that matters to you.
type Credentials ¶ added in v0.11.0
Credentials is the JSON body the login and register handlers decode.
type PasswordHasher ¶ added in v0.11.0
type PasswordHasher interface {
Hash(password string) (string, error)
Verify(password, hash string) bool
}
PasswordHasher hashes and verifies passwords. The default is PBKDF2-SHA256 from the standard library (OWASP parameters), chosen so the runtime keeps zero dependencies; swap in argon2id via this interface if your threat model asks for it.
var DefaultHasher PasswordHasher = pbkdf2Hasher{}
DefaultHasher is the PBKDF2-SHA256 hasher used when Auth.Hasher is nil. Hashes embed their parameters ("pbkdf2$<iterations>$<salt>$<key>"), so stored passwords keep verifying if the defaults change.
type SSEHub ¶
type SSEHub struct {
// contains filtered or unexported fields
}
SSEHub broadcasts events to every connected client. Register its ServeHTTP as a route handler and call Publish from anywhere:
var events = borgo.NewSSEHub()
//borgo:route GET /api/events
func Events(w http.ResponseWriter, r *http.Request) { events.ServeHTTP(w, r) }
type SSEStream ¶
type SSEStream struct {
// contains filtered or unexported fields
}
SSEStream is one open server-sent-events response.
func SSE ¶
SSE prepares the response for server-sent events and returns the stream. The front server proxies it to the browser without buffering.
func (*SSEStream) Done ¶
func (s *SSEStream) Done() <-chan struct{}
Done closes when the client disconnects or the server starts shutting down. A stream handler must return once it fires.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
borgogen
command
Command borgogen statically analyzes an app's api/ package (go/ast + go/types, no runtime reflection) and generates two files:
|
Command borgogen statically analyzes an app's api/ package (go/ast + go/types, no runtime reflection) and generates two files: |