# sr-ht-ecore Extended core for the custom services of a self-hosted SourceHut instance (compare, spec, dolt, cover, bench, ...). Everything these services share that is *ours* — not upstream's — lives here, so the sr-ht-core fork can stay a clean mirror of upstream core-go, and so the services stop carrying drifting copies of the same code. ## Packages - `chrome` — the shared page chrome: service-switcher nav built from the shared config.ini (`chrome.BuildNav`), per-request `chrome.Page` with login/logout/profile URLs against meta.sr.ht's unified login, embedded `srht-nav` / `srht-env-banner` template partials (circle brand + red service label + switcher + login box), and the generic template helpers (`dict`, `shortsha`). - `grants` — the grant vocabulary of tokens.sr.ht (SPEC ch. 3): `:` members split on ASCII whitespace, `*` for every action of every service, the reserved `id:` member a registered token carries, and the subset rule an exchange narrows by. Shared because the daemon that mints and every service that validates have to read one grammar — two parsers that disagree about what counts as a permission is a hole on the security path, not a cosmetic divergence. - `bearer` — the shared working-token validator (SPEC ch. 6), one copy for every service that accepts a tokens.sr.ht token: verify the signature, decide whether the token is ours, check the grant, and — only for a registered token — ask tokens.sr.ht whether it is still live, behind a 60s cache. Every step that can refuse locally runs before the one that cannot, so a short token never touches the network at all. ## Usage: chrome ```go svc := chrome.NewService(conf, "compare.sr.ht") svc.StyleHref = cssHref // after discovering the hashed stylesheet t := chrome.MustAttach(template.New("layout").Funcs(chrome.Funcs())) // ... parse the service's own templates into t ... page := svc.Page(r, "Page title", username) // username "" = anonymous ``` In the layout: ```html {{template "srht-env-banner" .}} ``` The template dot must expose the `chrome.Page` fields — either a `Page` itself, or a service view struct that embeds one (promoted fields resolve in templates). ## Usage: bearer ```go v, err := bearer.New(bearer.Options{ Origin: conf.Get("tokens.sr.ht", "origin"), // https://tokens.srht.bigb.es ClientID: "bench.sr.ht", // the CALLING service NodeID: hostname, }) tok, err := v.Validate(r.Context(), presented, "bench:upload") switch { case err == nil: // tok.Username, tok.Grants, tok.TokenID case errors.Is(err, bearer.ErrNotOurs): // service policy: accept as a meta.sr.ht PAT, or refuse case errors.Is(err, bearer.ErrForbidden): http.Error(w, "insufficient grants", http.StatusForbidden) case errors.Is(err, bearer.ErrUnavailable): http.Error(w, "token service unavailable", http.StatusServiceUnavailable) default: // ErrInvalid, ErrRevoked http.Error(w, "invalid token", http.StatusUnauthorized) } ``` `crypto.InitCrypto` must have run first — the signing key and the network key both live in that package's globals. Two of those arms are the ones to get right. `ErrNotOurs` is deliberately not decided by the validator: SPEC ch. 6 step 2 leaves it to each service whether a foreign bearer token is a meta PAT to accept (dolt) or something to refuse (bench, cover). And `ErrUnavailable` is **503, never 401** — reading an unreachable daemon as "revoked" would refuse live tokens across the instance for the length of a tokens.sr.ht restart. ## Policy: chrome The chrome bakes in the instance-wide decisions instead of parameterizing them: the switcher renders only for authenticated viewers; paste, pages and hub never appear in it; the brand is always circle + site name + red service label and links to the service's own root; the profile link prefers hub's `~username` page when hub is configured. Service-specific nav entries go through `Service.ExtraNav`; per-page width through `Page.ContainerClass`.