Files
zeekayandHanzo Dev 55aa586615 a product states which of its own routes a publishable key reaches
What such a key may reach was a list of addresses kept in the rule that
reads it. The list held Base's own routes and nothing else, so bootnode's
twelve reads — which it registers precisely to be reachable that way, and
guards with a read-only refusal beside each of its eleven writes —
answered 403 wherever the plugin was on.

A list of one package's addresses kept in another goes stale the moment
that package mounts a route, and it goes stale silently. So the answer
moves to the route: apis.Public declares one where it is registered, and
the rule holds no addresses at all. Bootnode declares its twelve reads;
Base declares its records, rest, files and health reads. The rule keeps
one address of its own, the public submit path, because whether it is
public is the collection's statement and not the route's.

A route needs to know its whole address for that, so the mux records it
as it mounts: Route.Pattern is the method, the group prefixes and the
path, the same string a request reports having matched.

Second layer, restored: bootnode read ReadOnly off the identity headers,
which the org plugin writes identically for every key it resolves — so a
publishable key arrived as a person and no read-only refusal could fire.
The headers say who; the credential says what kind.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-24 06:55:07 -07:00

60 lines
1.9 KiB
Go

package apis
import (
"net/http"
"sync"
"github.com/hanzoai/base/core"
"github.com/hanzoai/base/tools/router"
)
// declared are the routes a publishable credential may reach. It starts empty:
// an address nobody declared is an address such a credential is refused.
var declared struct {
sync.RWMutex
routes []*router.Route[*core.RequestEvent]
}
// Public declares that a publishable credential — a key printed in a web page —
// may reach these routes. It is stated where the route is registered:
//
// Public(g.GET("/chains", p.handleListChains))
//
// so that the product owning a route owns the answer about it, and the address
// is written once. The alternative was a list of another package's addresses
// kept in the rule: it went stale the moment a plugin mounted a route, which is
// how twelve reads meant to be reachable by such a key answered 403.
//
// The routes are read back by their mounted [router.Route.Pattern], which is
// assembled by the mux and is what a request reports having matched, so a
// pattern that merely ends the same way is a different route.
func Public(routes ...*router.Route[*core.RequestEvent]) {
declared.Lock()
declared.routes = append(declared.routes, routes...)
declared.Unlock()
}
// IsPublic reports whether the route a request matched was declared [Public].
//
// The declaration is held per process rather than per app, because the answer
// is read where neither the router that mounted the route nor the plugin that
// declared it is in scope. A server builds one router, so the two are the same
// set; a process that built two routers over the same addresses would read one
// answer for both.
func IsPublic(r *http.Request) bool {
if r.Pattern == "" {
return false
}
declared.RLock()
defer declared.RUnlock()
for _, route := range declared.routes {
if route.Pattern() == r.Pattern {
return true
}
}
return false
}