package bearer import ( "errors" "net/http" "strconv" ) // StatusFor is the instance's answer to "a token was refused — what does the // caller see?", as one table rather than one per service. // // The mapping itself is not subtle. What makes it worth sharing is the arm // that is easy to get wrong and impossible to notice: ErrUnavailable is 503, // never 401. Reading an unreachable token daemon as "revoked" tells every CI // job on the instance that its credential is bad for as long as tokens.sr.ht // takes to restart, and somebody spends the evening re-minting tokens that // were never broken. bench alone had this switch written out three times — in // its REST surface, its MCP surface and its resolver — which is three chances // to fold the unreachable case into the invalid one. // // The three refusals that ARE the caller's fault share one status and one // sentence on purpose: telling a prober "that token exists but is revoked" is // information they have not earned. Which of them it was belongs in the log. // // ErrNotOurs is the one arm a service must decide before asking: it means a // well-formed token from another issuer, almost certainly a meta.sr.ht PAT, // and SPEC ch. 6 step 2 leaves each service to accept it (dolt) or refuse it // (bench, cover). Handle it first; reaching here it is a refusal, so it maps // to 401. // // A nil error maps to 200 so a caller can write the status unconditionally. func StatusFor(err error) int { switch { case err == nil: return http.StatusOK case errors.Is(err, ErrForbidden): return http.StatusForbidden case errors.Is(err, ErrUnavailable): return http.StatusServiceUnavailable default: // ErrInvalid, ErrRevoked, ErrNotOurs, and anything a future validator // step adds: an unrecognised failure is the caller's credential, not // the instance's health. A new sentinel that deserves 503 has to say // so here, which is the point of the default going this way — a // forgotten arm refuses a request rather than declaring the service // unwell. return http.StatusUnauthorized } } // Challenge is the WWW-Authenticate value a 401 carries: the scheme, and the // service's own config section as the realm. // // RFC 9110 requires the header on a 401, and every service on the instance was // assembling the same string from the same constant. The realm is the section // name ("bench.sr.ht") because that is what identifies the service everywhere // else on this instance — in the config, in the nav, in a grant. // // The realm is quoted per RFC 9110 §11.6.1; a quote or backslash in it would // end the parameter early, so the value is escaped rather than trusted. In // practice a section name contains neither. func Challenge(realm string) string { return "Bearer realm=" + strconv.Quote(realm) }