Documentation
¶
Overview ¶
Package web holds the HTTP plumbing shared by every API handler: the JSON error envelope, request helpers, CSRF and the /api/v1 group.
Index ¶
- Constants
- Variables
- func BindJSON(c echo.Context, dst any) error
- func CSRF(secureCookie bool) echo.MiddlewareFunc
- func ErrorHandler() echo.HTTPErrorHandler
- func FormBool(c echo.Context, field string) (*bool, error)
- func FormDate(c echo.Context, field string) (*time.Time, error)
- func FormFile(c echo.Context, field string) (*upload.File, error)
- func FormInt(c echo.Context, field string) (*int, error)
- func FormString(c echo.Context, field string) *string
- func Health(c echo.Context) error
- func IPExtractor(trusted []*net.IPNet) echo.IPExtractor
- func IsAppRoute(p string) bool
- func LoggedIn(c echo.Context) bool
- func MountRedirects(e *echo.Echo)
- func MountSPA(e *echo.Echo, files fs.FS, proxy *url.URL)
- func NewAPI(e *echo.Echo, secureCookies bool) *echo.Group
- func Origin(rawURL string) string
- func ParamID(c echo.Context, name string) (int, error)
- func ParseCIDRs(raw string) ([]*net.IPNet, error)
- func RedactPath(p string) string
- func RedactSpanPath(next echo.HandlerFunc) echo.HandlerFunc
- func RequireForm(c echo.Context) error
- func SecurityHeaders(hsts, csp bool, fileOrigin string) echo.MiddlewareFunc
- type APIError
- type Actor
- type ErrorResponse
- type Guards
- type HealthResponse
Constants ¶
const APIPrefix = "/api/"
APIPrefix is the path prefix whose errors are rendered as JSON.
const ActorKey = "web.actor"
ActorKey is set on the request context once a guard has loaded a logged-in user, alongside LoggedInKey. It carries just enough of the user for audit logging and per-role field filtering in domain packages that can't import auth (auth imports user, so user can't import auth back without a cycle).
const DateLayout = "2006-01-02"
DateLayout is the date-only format API inputs use (dates without times, e.g. date of birth or date of event).
const LoggedInKey = "web.loggedIn"
LoggedInKey is set on the request context once a guard has loaded a logged-in user.
Variables ¶
var SPARoutes = []string{
"/",
"/teams", "/teams/new", "/team/:id", "/team/:id/edit",
"/news", "/news/new", "/news/:id", "/news/:id/edit",
"/whatson", "/whatson/new", "/whatson/:id", "/whatson/:id/edit",
"/gallery", "/documents", "/programmes", "/sponsors", "/players", "/users",
"/info", "/info/edit", "/contact", "/account", "/reset/:token", "/privacy", "/design",
}
SPARoutes are the React client's routes (client/App.tsx; a test keeps them in step). Any other page path still gets the app, which shows "Page not found", but with a 404 status so crawlers and link checkers see it.
Functions ¶
func CSRF ¶
func CSRF(secureCookie bool) echo.MiddlewareFunc
CSRF protects unsafe methods. Browsers that send Sec-Fetch-Site: same-origin pass without a token; other clients must echo the _csrf cookie in X-CSRF-Token. Every failure is a 403.
func ErrorHandler ¶
func ErrorHandler() echo.HTTPErrorHandler
ErrorHandler renders errors on /api/* as the JSON envelope, and every other path as a small HTML page (the web client handles its own pages; this covers redirects, /download and anything else the server answers directly).
func FormString ¶
FormString returns the form value for field, or nil when the field was not sent at all (as opposed to sent empty).
Unlike echo's own FormParams (which merges the URL query string into request.Form), this reads only the request body: request.PostForm holds urlencoded body values plus, after ParseMultipartForm, the multipart text fields. That keeps a query parameter of the same name from masquerading as a form field, which matters for PATCH's absent-vs-sent semantics (FormBool and FormDate rely on this too, via FormString).
func Health ¶
Health reports that the server is up.
@Summary Health check
@Tags system
@Produce json
@Success 200 {object} web.HealthResponse
@Router /health [get]
func IPExtractor ¶
func IPExtractor(trusted []*net.IPNet) echo.IPExtractor
IPExtractor decides which address c.RealIP() reports. X-Forwarded-For is believed only when the request comes from one of the trusted proxy ranges; otherwise anyone could pick their own IP, dodging the per-IP sign-in limit and faking the address in logs. With no trusted ranges the connecting address is used and the header is ignored.
func IsAppRoute ¶
IsAppRoute reports whether p matches one of SPARoutes; ":name" matches one non-empty path segment.
func MountRedirects ¶
MountRedirects keeps classic-site and /app URLs working now that the web client is served at the root. Only GET and HEAD are redirected. /logout deliberately doesn't sign out: a GET link would let any site sign people out. Signing out is POST /api/v1/auth/logout.
func MountSPA ¶
MountSPA serves the React client at the site root, leaving /api alone. With proxy set (development) every non-API request is reverse-proxied to the Vite dev server, including HMR websockets. Otherwise files (the built client; may be nil) are served: hashed assets, root files, and index.html for page paths (200 for known app routes, 404 otherwise).
func NewAPI ¶
NewAPI mounts /api and returns the /api/v1 group, protected by CSRF. Unknown /api paths are JSON 404s rather than the web client's not-found page.
func Origin ¶
Origin returns the scheme and host of rawURL (e.g. the S3 endpoint), or "" when it has none.
func ParseCIDRs ¶
ParseCIDRs parses a comma-separated list of IP addresses and CIDR ranges, e.g. the TRUSTED_PROXIES setting "10.20.1.10,10.20.1.11" or "10.20.0.0/16". A bare address means just that one host.
func RedactPath ¶
RedactPath replaces a reset token in p with ":token". Other paths are returned unchanged. p may be a site path, an /api/v1 path, or an API path relative to /api/v1 as the client reports it.
func RedactSpanPath ¶
func RedactSpanPath(next echo.HandlerFunc) echo.HandlerFunc
RedactSpanPath overwrites the url.path the tracing middleware recorded when it holds a reset token. Register it after the tracing middleware.
func RequireForm ¶
RequireForm rejects a request whose Content-Type isn't a form: without it, web.FormString and friends silently return nil for a JSON (or any other) body, so a write with the wrong Content-Type would look like "no fields sent" and succeed doing nothing instead of failing loudly. Handlers that read multipart or urlencoded form fields must call this first.
func SecurityHeaders ¶
func SecurityHeaders(hsts, csp bool, fileOrigin string) echo.MiddlewareFunc
SecurityHeaders sets the browser protections on every response. hsts is for sites served only over HTTPS. csp is off in development, where the Vite dev server needs inline scripts and websockets; the Swagger UI under /api/v1/swagger never gets it, for the same reason. fileOrigin is where uploaded files are served from (see Origin).
Types ¶
type APIError ¶
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Fields map[string]string `json:"fields,omitempty"`
}
APIError is the body of every API error response.
type Actor ¶
type Actor struct {
ID int
Email string
Role role.Role
// TeamID is the team a Manager manages; zero for other roles.
TeamID int
}
Actor is the logged-in user making a request.
type ErrorResponse ¶
type ErrorResponse struct {
Error APIError `json:"error"`
}
ErrorResponse wraps APIError as {"error": {...}}.
type Guards ¶
type Guards struct {
Login echo.MiddlewareFunc // any logged-in user
Editor echo.MiddlewareFunc // logged in, not Manager or Photographer
NotManager echo.MiddlewareFunc // logged in, not Manager (gallery)
Players echo.MiddlewareFunc // logged in, an editor but not Programme Editor
ClubSecretaryHigher echo.MiddlewareFunc // Safeguarding Officer, Club Secretary, Chairperson, Webmaster
Identify echo.MiddlewareFunc // loads the user if logged in; never rejects
}
Guards are the authorisation middlewares handlers attach to routes. They are built by the auth package; web only defines the shape so domain packages don't import auth.
type HealthResponse ¶
type HealthResponse struct {
Status string `json:"status"`
}
HealthResponse is the body of GET /health.