- lighthouse: Service/From with realtime.driver selection, RegisterDriver registry, null/log/memory drivers, Route/Surface/Mount, users and actors - centrifugo: HTTP API client (apikey header, 2xx success, no request without a key), five-generator HS256 TokenIssuer, TokenHandler with the WinterCMS 401/503 bodies - module README and root modules table row
143 lines
3.8 KiB
Go
143 lines
3.8 KiB
Go
package lighthouse
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
)
|
|
|
|
// Surface says who calls a driver route, so the application can put it
|
|
// behind the right guard and group.
|
|
type Surface int
|
|
|
|
const (
|
|
// UserAuth routes are called by a signed-in browser user; they mount
|
|
// behind the application's user guard.
|
|
UserAuth Surface = iota + 1
|
|
// ServerToServer routes are called by the realtime server itself; they
|
|
// mount in a raw group without the house envelope.
|
|
ServerToServer
|
|
// Public routes need no authentication.
|
|
Public
|
|
)
|
|
|
|
// String returns the surface name.
|
|
func (s Surface) String() string {
|
|
switch s {
|
|
case UserAuth:
|
|
return "UserAuth"
|
|
case ServerToServer:
|
|
return "ServerToServer"
|
|
case Public:
|
|
return "Public"
|
|
default:
|
|
return fmt.Sprintf("Surface(%d)", int(s))
|
|
}
|
|
}
|
|
|
|
// Route is an HTTP endpoint declared by a driver.
|
|
type Route struct {
|
|
// Name identifies the route inside the driver (for example "token").
|
|
Name string
|
|
// Method is GET, POST, PUT, PATCH or DELETE.
|
|
Method string
|
|
// Path is the absolute request path.
|
|
Path string
|
|
Surface Surface
|
|
Handler http.HandlerFunc
|
|
}
|
|
|
|
// Surfaces holds the application's middleware for each surface. Middleware
|
|
// is appended after the surface middleware on every route, so a guard in
|
|
// UserAuth runs before a throttle in Middleware.
|
|
type Surfaces struct {
|
|
UserAuth []string
|
|
ServerToServer []string
|
|
Public []string
|
|
Middleware []string
|
|
}
|
|
|
|
// Mount registers the driver's routes on r. UserAuth and Public routes go
|
|
// through r.Group and ServerToServer routes through r.GroupRaw, each with
|
|
// the surface middleware followed by s.Middleware. A nil driver, or one
|
|
// without routes, mounts nothing. A UserAuth route with an empty
|
|
// s.UserAuth is an error: the application must never expose a user route
|
|
// without a guard. Every route is validated before any is registered.
|
|
func Mount(r pact.Router, d Driver, s Surfaces) error {
|
|
if r == nil {
|
|
return fmt.Errorf("lighthouse: router is nil")
|
|
}
|
|
if d == nil {
|
|
return nil
|
|
}
|
|
routes := d.Routes()
|
|
for _, rt := range routes {
|
|
if err := validateRoute(d, rt, s); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, rt := range routes {
|
|
mw := append(append([]string{}, surfaceMiddleware(rt.Surface, s)...), s.Middleware...)
|
|
add := func(g pact.Router) { addRoute(g, rt) }
|
|
if rt.Surface == ServerToServer {
|
|
r.GroupRaw("/", mw, add)
|
|
} else {
|
|
r.Group("/", mw, add)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateRoute(d Driver, rt Route, s Surfaces) error {
|
|
where := fmt.Sprintf("lighthouse: driver %s route %q", d.Name(), rt.Name)
|
|
if rt.Handler == nil {
|
|
return fmt.Errorf("%s has no handler", where)
|
|
}
|
|
if !strings.HasPrefix(rt.Path, "/") {
|
|
return fmt.Errorf("%s path %q must be absolute", where, rt.Path)
|
|
}
|
|
switch strings.ToUpper(rt.Method) {
|
|
case http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
|
|
default:
|
|
return fmt.Errorf("%s has unsupported method %q", where, rt.Method)
|
|
}
|
|
switch rt.Surface {
|
|
case UserAuth:
|
|
if len(s.UserAuth) == 0 {
|
|
return fmt.Errorf("%s is a UserAuth route but Surfaces.UserAuth is empty; mount it behind a user guard", where)
|
|
}
|
|
case ServerToServer, Public:
|
|
default:
|
|
return fmt.Errorf("%s has unknown surface %v", where, rt.Surface)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func surfaceMiddleware(surface Surface, s Surfaces) []string {
|
|
switch surface {
|
|
case UserAuth:
|
|
return s.UserAuth
|
|
case ServerToServer:
|
|
return s.ServerToServer
|
|
default:
|
|
return s.Public
|
|
}
|
|
}
|
|
|
|
func addRoute(g pact.Router, rt Route) {
|
|
switch strings.ToUpper(rt.Method) {
|
|
case http.MethodGet:
|
|
g.Get(rt.Path, rt.Handler)
|
|
case http.MethodPost:
|
|
g.Post(rt.Path, rt.Handler)
|
|
case http.MethodPut:
|
|
g.Put(rt.Path, rt.Handler)
|
|
case http.MethodPatch:
|
|
g.Patch(rt.Path, rt.Handler)
|
|
case http.MethodDelete:
|
|
g.Delete(rt.Path, rt.Handler)
|
|
}
|
|
}
|