Public: anyone can read this; nobody can change it.
Connecting a token that lacks a scope names the scope, and the token form lists what a token needs #70
Check · Review · Land · Live — No review yet.
Forgejo and Gitea answer a token that lacks a required scope with 403 and a body that names the scope: `token does not have at least one of required scope(s): [read:user]`. The API client mapped every 403 to "the token may not do that on the origin" and did not read the body. Connect then said "That token was refused by the origin". That is the same sentence for a wrong token, an expired token and…
0 files · +0 −0
waiting checks and the preview wait for a writer to approve this head
Check on trevato-apps/forge: running, as of
Checks and the preview wait for a writer to approve this head
A guest
This pull comes from a guest: its preview runs without the app's variables or a forge token until a person who can write here approves it.
This pull cannot land as it is, whatever its review says, because it changes guarded paths: (the diff) (it could not be read).
Forgejo and Gitea answer a token that lacks a required scope with 403 and a body that names the scope:
token does not have at least one of required scope(s): [read:user]. The API client mapped every 403 to "the token may not do that on the origin" and did not read the body. Connect then said "That token was refused by the origin". That is the same sentence for a wrong token, an expired token and a narrow token, so a person could not tell which one they had. A narrow token is the usual case: Forgejo's token page asks for each scope, and a forge's whoami needs User: Read.ApiClientnow reads the body of a 403 (4 KB at most) and keeps only the words that look like scopes (missingScopes). Nothing else from the origin goes into a message. The failure carriesmissingScopes, and its message names them as Forgejo's token page does (scopeWords): "the token is missing User: Read". Sync errors say so too, for example a label import without the issue scope.Connecting with such a token answers
missing_scopes(422) with "This token is missing User: Read." and what a token needs.connectRefusalgives the API and the web form one code and one sentence for each refused connect. An OAuth grant that lacks a scope says so, instead of "the new token did not work".The token form lists what a Forgejo or Gitea token needs: User: Read; Repository: Read and write; Issue: Read and write; nothing else. When the instance is known, it links to Forgejo 15's
/user/settings/applications/tokens/newwith the name and the scopes already chosen. Gitea and older Forgejo get the list only.A token with all its scopes, a wrong token and an expired token behave as before. Every other refused connect keeps its code and its sentence.
connectOrigin's error is nowConnectError | ScopeRefusal, so seven test call sites that didnew Error(connected.error)now stringify it.Tests
bun 1.3.13, a scrubbed environment (no agent variables):
test/origin-token-scopes.test.ts: 5 pass, 0 fail: the body parser (a matching body, an unrelated body, junk inside the brackets), the scope words, connect and the API refusing a token withoutread:userwith nothing stored, and the form's list and link.test/fake-origin.tsgainsuserScopeMissing, which answers/useras Forgejo does.bun test --timeout 60000call: 431 pass, 0 fail across 32 files.bun scripts/gate.tsandtsc --noEmit: clean.the 32 files
origin-token-scopes, origins, origins-oauth, origins-operator, origins-reconnect, origins-security-auth, origins-security-provider, origins-security-push, origins-sync, origins-sync-settled, web-origins, web-origin-link, mcp-origins, mcp-origin-apps, push-through, trusted-hosts, trusted-hosts-ui, steps-lists, no-polling, refresh-meta, mirror-outbound, mirror-out, carried, migrate, migrate-after, tui-migrate, apps-feedback-mirror, and the five cross files reserved-names, studio, shards, check-list, hollow (each
test/<name>.test.ts).🤖 Generated with Claude Code