feat(auth): guide login, readable status and switch without --host

tea auth login without a method flag asks on a terminal for the instance
(prefilled from the repository, default_host or the only instance), how
to log in (token, browser, SSH key), the pasted token without echo with
a link to the applications page, where to keep it and whether git
should use tea for the instance. Bare hosts are accepted. Logging in to
an account tea already has replaces its credential after a
confirmation, or with --yes; a named account is confirmed before the
token is asked for. Every login ends with "Logged in to HOST as USER
(token, keyring)". OAuth requests write scopes for everything but
administration by default, and --scopes chooses others; --web is an
alias of --oauth.

auth status prints a block per instance with the active account, how
each account authenticates, its expiry, verification and token scopes,
whether git uses tea, and the GITEA_TOKEN of the environment. Piped it
prints tab-separated fields. It fails with exit code 4 when no account
or no usable credential exists instead of printing nothing.

auth switch takes the instance of the repository, --host, default_host
or the sole instance instead of requiring --host, toggles between two
accounts, asks on a terminal with more, and accepts --user. auth logout
takes the account as an argument, names it in its errors, and asks git's
other credential helpers to forget what they stored for the account.
auth refresh points token accounts to tea auth login --with-token, and
the new auth token prints the token of an account for scripts.

Every auth command reports what it did on a terminal and has a long
help text and examples.

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-11 23:50:44 +02:00
1 parent 60de407eca
commit a2bea8da23
13 files changed
+1421 -327

No files matched your search

+24
View File
@@ -42,6 +42,30 @@ func CurrentAccount(ctx context.Context, client *transport.Client) (string, erro
return account, nil
}
// TokenScopes returns the scopes of the access token that client authenticates with, such as
// "all" or "write:repository". Gitea answers only for access tokens.
func TokenScopes(ctx context.Context, client *transport.Client) ([]string, error) {
token, _, err := call(ctx, client, func(ctx context.Context, c *gitea.Client) (*gitea.CurrentAccessToken, *gitea.Response, error) {
return c.Users.GetCurrentAccessToken(ctx)
})
if err != nil {
return nil, err
}
scopes := make([]string, len(token.Scopes))
for i, scope := range token.Scopes {
scopes[i] = string(scope)
}
return scopes, nil
}
// ServerVersion returns the Gitea version of the instance.
func ServerVersion(ctx context.Context, client *transport.Client) (string, error) {
version, _, err := call(ctx, client, func(ctx context.Context, c *gitea.Client) (string, *gitea.Response, error) {
return c.Meta.ServerVersion(ctx)
})
return version, err
}
// MatchAccount reports whether selected matches the authenticated login.
// An empty selection accepts any account.
func MatchAccount(selected, authenticated string) bool {
+22
View File
@@ -79,3 +79,25 @@ func TestMatchAccount(t *testing.T) {
require.True(t, MatchAccount("alice", "Alice"))
require.False(t, MatchAccount("bob", "Alice"))
}
func TestTokenScopesAndServerVersion(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/token":
require.Equal(t, "token secret", r.Header.Get("Authorization"))
_, _ = w.Write([]byte(`{"id":1,"name":"tea","scopes":["write:repository","read:user"]}`))
case "/api/v1/version":
_, _ = w.Write([]byte(`{"version":"28.1.0"}`))
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer server.Close()
client := transport.New(server.URL, "secret", false)
scopes, err := TokenScopes(context.Background(), client)
require.NoError(t, err)
require.Equal(t, []string{"write:repository", "read:user"}, scopes)
version, err := ServerVersion(context.Background(), client)
require.NoError(t, err)
require.Equal(t, "28.1.0", version)
}