~bigbes/sr-ht-dolt

ref: b525a62b754afa38cc1aee0995eddae5f8a37214 sr-ht-dolt/authn/bearer_test.go -rw-r--r-- 12.1 KiB
b525a62b — Eugene Blikh a day ago
authn: name the grant scope dolt.sr.ht/DATABASES

api-meta.json published the scope as lowercase "repos" while every other
service on the instance publishes it upper case — git.sr.ht/REPOSITORIES,
todo.sr.ht/TRACKERS, paste.sr.ht/PASTES, builds.sr.ht/JOBS. Upstream derives
those from a GraphQL enum; this service has no @access directive to derive
from, so the spelling was free and drifted.

It matters because meta.sr.ht compares the string verbatim. Its oauth2
blueprint fetches every service's api-meta.json once at import time and
validates a requested grant with `scope in service_scopes[svc]` — no case
folding, no aliasing. So a grant typed by hand as dolt.sr.ht/REPOS:RO was
refused, by analogy with every neighbouring service.

DATABASES rather than REPOS: that is what the surface calls the object
everywhere a user meets it — the GraphQL databases connection, the web pages,
the docs. The storage layer underneath still says "repo"; renaming that is a
deeper change and is not what a token grant names.

No backward compatibility. auth.Grants.Has is a map lookup, so a PAT minted
against the old spelling is refused rather than quietly honoured, and two table
cases assert that instead of leaving it implied. Deploying this is two-sided:
meta.sr.ht has to be restarted before anyone can mint a token carrying the new
name.

The federation gateway is unaffected — it forwards the client's Authorization
header to each service and never reads api-meta.json.

sr-ht-dolt-xic
6288fc1e — Eugene Blikh 7 days ago
authn: accept tokens.sr.ht working tokens and PATs as bearer

The /mcp surface is bearer-only, and none of the three credential planes
this service has fits an agent. ResolveBearer adds the fourth: it decodes
the presented token once, locally, and routes on the ClientID — the only
thing that tells a tokens.sr.ht working token from a meta.sr.ht PAT, since
both are sealed with the same instance key.

A working token goes through sr-ht-ecore's validator (one copy of that
check for the whole instance) and carries its grants out on the result, so
the surface can ask Authorize where the action is known. A PAT reuses
ResolveBasic with the token's own username as the identity — there is no
presented username to compare against in a bearer header — and the same
TokenGrantsAllow read gate the clone path applies.

An instance with no [tokens.sr.ht] section passes a nil validator: meta
PATs and anonymity keep working, a working token is refused, because a
machine credential this instance cannot verify is refused rather than
guessed at. A failed credential is always a refusal and never a downgrade
to anonymous, and backend.go's two error classes are preserved so a caller
can still answer 401 against 503.

core.GrantRead is the vocabulary tokens.sr.ht deliberately does not know.
There is no dolt:write: nothing on that surface writes, and a grant nobody
checks is a promise to an operator that no code keeps.