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)
}