mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
7a6a4dbdf5
* fix: preserve credential and config read failures Report corrupt or inaccessible stored credentials as typed errors instead of treating them as missing. Preserve config load failures across auth diagnostics and keep trusted request metadata available when host signal collection is unavailable. Fixes #1925 * fix(auth): attach re-authorization recovery to corrupt stored token errors A stored token that fails to decode or fails semantic validation is now reported as internal/storage but carried no recovery hint, so `api --as user`, `auth check`, `auth status`, and `doctor` all ended in a dead end. A new login overwrites the damaged entry, so attach the canonical user-authorization recovery on the two read-side corruption branches. The write-side validator stays hint-free: a rejected write leaves nothing on disk to re-authorize. `auth status` keeps the storage failure in `note` and appends the recovery instead of replacing it. Tests pin the hint on GetStoredToken, GetValidAccessToken, identitydiag (including reduced-distribution projection when auth login is concealed), auth check, and auth status, and pin that SetStoredToken replaces a corrupt entry. ---------
113 lines
3.0 KiB
Go
113 lines
3.0 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package auth
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
larkauth "github.com/larksuite/cli/internal/auth"
|
|
"github.com/larksuite/cli/internal/cmdutil"
|
|
"github.com/larksuite/cli/internal/core"
|
|
"github.com/larksuite/cli/internal/output"
|
|
"github.com/larksuite/cli/internal/recovery"
|
|
)
|
|
|
|
// CheckOptions holds all inputs for auth check.
|
|
type CheckOptions struct {
|
|
Factory *cmdutil.Factory
|
|
Scope string
|
|
JSON bool
|
|
}
|
|
|
|
// NewCmdAuthCheck creates the auth check subcommand.
|
|
func NewCmdAuthCheck(f *cmdutil.Factory, runF func(*CheckOptions) error) *cobra.Command {
|
|
return newCmdAuthCheck(f, runF, nil)
|
|
}
|
|
|
|
func newCmdAuthCheck(
|
|
f *cmdutil.Factory,
|
|
runF func(*CheckOptions) error,
|
|
projector *recovery.Projector,
|
|
) *cobra.Command {
|
|
opts := &CheckOptions{Factory: f}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "check",
|
|
Short: "Check if current token has specified scopes",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if runF != nil {
|
|
return runF(opts)
|
|
}
|
|
return authCheckRunWithRecovery(opts, projector)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVar(&opts.Scope, "scope", "", "scopes to check (space-separated)")
|
|
cmd.Flags().BoolVar(&opts.JSON, "json", false, "structured JSON output")
|
|
cmd.MarkFlagRequired("scope")
|
|
cmdutil.SetRisk(cmd, "read")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func authCheckRun(opts *CheckOptions) error {
|
|
return authCheckRunWithRecovery(opts, nil)
|
|
}
|
|
|
|
func authCheckRunWithRecovery(opts *CheckOptions, projector *recovery.Projector) error {
|
|
f := opts.Factory
|
|
|
|
required := strings.Fields(opts.Scope)
|
|
if len(required) == 0 {
|
|
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--scope cannot be empty").WithParam("--scope")
|
|
}
|
|
|
|
config, err := f.Config()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if config.UserOpenId == "" {
|
|
output.PrintJson(f.IOStreams.Out, map[string]interface{}{"ok": false, "error": "not_logged_in", "missing": required})
|
|
return output.ErrBare(1)
|
|
}
|
|
|
|
stored, err := larkauth.GetStoredToken(config.AppID, config.UserOpenId)
|
|
if err != nil {
|
|
return f.PresentError(err, cmdutil.ErrorPresentationOptions{
|
|
Projector: projector,
|
|
Identity: core.AsUser,
|
|
})
|
|
}
|
|
if stored == nil {
|
|
output.PrintJson(f.IOStreams.Out, map[string]interface{}{"ok": false, "error": "no_token", "missing": required})
|
|
return output.ErrBare(1)
|
|
}
|
|
|
|
missing := larkauth.MissingScopes(stored.Scope, required)
|
|
missingSet := make(map[string]bool, len(missing))
|
|
for _, s := range missing {
|
|
missingSet[s] = true
|
|
}
|
|
var granted []string
|
|
for _, s := range required {
|
|
if !missingSet[s] {
|
|
granted = append(granted, s)
|
|
}
|
|
}
|
|
|
|
ok := len(missing) == 0
|
|
result := map[string]interface{}{"ok": ok, "granted": granted, "missing": missing}
|
|
if len(missing) > 0 && projector.CanReference(recovery.TargetAuthLogin) {
|
|
result["suggestion"] = projector.RenderHint(recovery.UserAuthorization(missing...))
|
|
}
|
|
output.PrintJson(f.IOStreams.Out, result)
|
|
if !ok {
|
|
return output.ErrBare(1)
|
|
}
|
|
return nil
|
|
}
|