mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-09-01 14:53:07 +08:00
4c26bc9ecc
Two halves of v0.3 roadmap item 3-2. (1) `weknora auth refresh` — explicit token renewal: Reads the stored refresh_token, spends it via POST /api/v1/auth/refresh (OAuth refresh-token grant), and persists both new tokens. API-key contexts rejected with input.invalid_argument (no refresh semantic). NOTE: gh CLI has `gh auth refresh` but with different semantics — gh's variant is an OAuth scope expansion / re-prompt via the browser (verified against the gh manual). The two share a name but solve different problems; there's no direct gh parallel for refresh-token grant because gh's PAT/OAuth-app model doesn't expose a short-lived access_token + refresh_token pair to clients. Error mapping: - no current context → auth.unauthenticated - --name unknown → local.context_not_found - missing refresh in keyring → auth.token_expired (hint: re-login) - server Success=false → auth.token_expired - network → network.error Envelope omits the token values (would leak into agent transcripts). (2) AuthRetryTransport — transparent retry: Wraps the SDK http.Client. On a 401 from a non-/auth/* endpoint: - JWT context: read refresh token, hit /auth/refresh, persist new pair, replay original request with new bearer. - API-key context: pass through (no refresh semantic). - Non-replayable body (req.GetBody == nil): pass through. - /auth/login or /auth/refresh: pass through (no recursion). Concurrent 401s are singleflight-coalesced via sync.Mutex — 5 parallel calls trigger exactly 1 refresh. SDK additions (additive, non-breaking): - WithTransport(rt http.RoundTripper) ClientOption. - PathAuthLogin / PathAuthRefresh constants (cli/internal/cmdutil/authretry imports them so the CLI and SDK can't drift on path strings). Refactor surfaced by the post-commit reviewer round: - cmdutil.RefreshAndPersist(ctx, store, refresher, ctxName) — the load-refresh → call-SDK → persist-pair sequence was duplicated between the standalone `auth refresh` and the transport's refresh closure; collapsed to one canonical implementation. - refreshFn signature takes context.Context so Ctrl+C during a transparent refresh cancels. - AuthRetryTransport.CurrentToken() removed — never called. 8 + 8 + 8 unit tests cover happy path / refresh-fail / auth-endpoint skip / api-key passthrough / singleflight under concurrency / non- replayable-body fallback. Roadmap: 3-2.
32 lines
1.0 KiB
Go
32 lines
1.0 KiB
Go
// Package auth holds the cobra commands for authentication
|
|
// (login / logout / list / refresh / status).
|
|
package auth
|
|
|
|
import (
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/Tencent/WeKnora/cli/internal/cmdutil"
|
|
)
|
|
|
|
// NewCmdAuth builds the `weknora auth` command tree and registers its
|
|
// subcommands. Called from cli/cmd/root.go.
|
|
func NewCmdAuth(f *cmdutil.Factory) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "auth",
|
|
Short: "Manage authentication credentials and contexts",
|
|
// NoArgs makes cobra emit its canonical `unknown command "X" for
|
|
// "weknora auth"` for any positional, which mapCobraError tags as
|
|
// FlagError → exit 2. Run (not RunE) is required: a parent with
|
|
// neither Run nor RunE short-circuits to help and skips Args
|
|
// validation entirely.
|
|
Args: cobra.NoArgs,
|
|
Run: func(c *cobra.Command, _ []string) { _ = c.Help() },
|
|
}
|
|
cmd.AddCommand(NewCmdLogin(f, nil))
|
|
cmd.AddCommand(NewCmdLogout(f))
|
|
cmd.AddCommand(NewCmdList(f))
|
|
cmd.AddCommand(NewCmdRefresh(f))
|
|
cmd.AddCommand(NewCmdStatus(f))
|
|
return cmd
|
|
}
|