mirror of
https://github.com/larksuite/cli.git
synced 2026-07-05 15:47:54 +08:00
* fix(identitydiag): harden verify path and tighten status semantics Follow-ups to #957: - bound bot/user verify calls with a 10s timeout (mirrors the doctor endpoint probe) so a hanging server cannot wedge `auth status --verify` or `doctor` - return StatusNotConfigured (not StatusMissing) when the user-identity path is blocked by missing app config, matching the bot side - surface the `{code, msg}` envelope on bot-info HTTP 4xx responses so callers see why bot auth was rejected, not just the bare HTTP code - introduce identity{User,Bot,None} constants in cmd/auth/status.go and use the exported StatusMessage() in the human-readable note instead of raw status codes like "not_configured" - collapse the duplicated verify-failed identity construction in the user path into a local helper - cover the new failure paths with unit tests (HTTP 4xx with envelope, business error code, user server-rejected, expired user token, strict-mode user-only, missing app config for user) Change-Id: I581348a65f15b1452a6f48a3e3245d09257314ac * fix(identitydiag): decode bot/v3/info from "bot" field, not "data" `/open-apis/bot/v3/info` returns `{code, msg, bot: {...}}` — the bot payload is under `bot`, not `data` as the newer Lark API convention would suggest. The decoder was reading from a non-existent `data` field, so `envelope.Data.OpenID` was always empty and every successful verify was reported as `Bot identity: verify failed: open_id is empty`. The pre-existing test mocks used `{"data": {...}}` matching the buggy decoder, so unit tests passed while production reads of every Lark account failed verification. Fix: - change the JSON tag on the envelope from `json:"data"` to `json:"bot"` - update mocks in identitydiag and cmd/auth/status tests to emit `bot` Verified locally: `lark-cli doctor` now reports `bot_identity: pass` for both a normal account and a bot-only profile, restoring the behavior that #957 set out to deliver. Change-Id: Ib26dfdd5a0cc37d2d62537ae2bf5e854e67cb83c * fix(shortcuts/common): decode bot/v3/info from "bot" field, not "data" Same schema bug as the one fixed in identitydiag — `RuntimeContext. fetchBotInfo` reads from a non-existent "data" key, so every successful call would report "open_id is empty" once a caller starts depending on it. There are no production callers of `RuntimeContext.BotInfo()` yet (only tests + the `TestNewRuntimeContextWithBotInfo` helper), so this bug is dormant — but the pre-existing tests pass with the same wrong schema in their mocks, so the first real consumer would silently break. Fix: tag `json:"data"` → `json:"bot"` plus aligning the four mock fixtures in runner_botinfo_test.go. The Go field name `Data` is kept to minimize the diff; only the JSON contract is corrected. Change-Id: I11e1e871603e5349f8df29b1d58e35d07b628dfd
141 lines
3.5 KiB
Go
141 lines
3.5 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package auth
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/larksuite/cli/internal/cmdutil"
|
|
"github.com/larksuite/cli/internal/identitydiag"
|
|
"github.com/larksuite/cli/internal/output"
|
|
)
|
|
|
|
// StatusOptions holds all inputs for auth status.
|
|
type StatusOptions struct {
|
|
Factory *cmdutil.Factory
|
|
Verify bool
|
|
}
|
|
|
|
// NewCmdAuthStatus creates the auth status subcommand.
|
|
func NewCmdAuthStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Command {
|
|
opts := &StatusOptions{Factory: f}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "status",
|
|
Short: "View current auth status",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if runF != nil {
|
|
return runF(opts)
|
|
}
|
|
return authStatusRun(opts)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVar(&opts.Verify, "verify", false, "verify token against server (requires network)")
|
|
cmdutil.SetRisk(cmd, "read")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func authStatusRun(opts *StatusOptions) error {
|
|
f := opts.Factory
|
|
|
|
config, err := f.Config()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defaultAs := config.DefaultAs
|
|
if defaultAs == "" {
|
|
defaultAs = "auto"
|
|
}
|
|
result := map[string]interface{}{
|
|
"appId": config.AppID,
|
|
"brand": config.Brand,
|
|
"defaultAs": defaultAs,
|
|
}
|
|
|
|
diagnostics := identitydiag.Diagnose(context.Background(), f, config, opts.Verify)
|
|
result["identities"] = diagnostics
|
|
result["identity"] = effectiveIdentity(diagnostics)
|
|
addLegacyUserFields(result, diagnostics.User)
|
|
addEffectiveVerification(result, diagnostics)
|
|
addStatusNote(result, diagnostics)
|
|
|
|
output.PrintJson(f.IOStreams.Out, result)
|
|
return nil
|
|
}
|
|
|
|
const (
|
|
identityUser = "user"
|
|
identityBot = "bot"
|
|
identityNone = "none"
|
|
)
|
|
|
|
func effectiveIdentity(d identitydiag.Result) string {
|
|
switch {
|
|
case d.User.Available:
|
|
return identityUser
|
|
case d.Bot.Available:
|
|
return identityBot
|
|
default:
|
|
return identityNone
|
|
}
|
|
}
|
|
|
|
func addLegacyUserFields(result map[string]interface{}, user identitydiag.Identity) {
|
|
if user.OpenID == "" {
|
|
return
|
|
}
|
|
result["userName"] = user.UserName
|
|
result["userOpenId"] = user.OpenID
|
|
if user.TokenStatus != "" {
|
|
result["tokenStatus"] = user.TokenStatus
|
|
}
|
|
if user.Scope != "" {
|
|
result["scope"] = user.Scope
|
|
}
|
|
if user.ExpiresAt != "" {
|
|
result["expiresAt"] = user.ExpiresAt
|
|
}
|
|
if user.RefreshExpiresAt != "" {
|
|
result["refreshExpiresAt"] = user.RefreshExpiresAt
|
|
}
|
|
if user.GrantedAt != "" {
|
|
result["grantedAt"] = user.GrantedAt
|
|
}
|
|
}
|
|
|
|
func addEffectiveVerification(result map[string]interface{}, d identitydiag.Result) {
|
|
switch result["identity"] {
|
|
case identityUser:
|
|
if d.User.Verified != nil {
|
|
result["verified"] = *d.User.Verified
|
|
if !*d.User.Verified {
|
|
result["verifyError"] = d.User.Message
|
|
}
|
|
}
|
|
case identityBot:
|
|
if d.Bot.Verified != nil {
|
|
result["verified"] = *d.Bot.Verified
|
|
if !*d.Bot.Verified {
|
|
result["verifyError"] = d.Bot.Message
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func addStatusNote(result map[string]interface{}, d identitydiag.Result) {
|
|
switch {
|
|
case !d.User.Available && d.Bot.Available:
|
|
result["note"] = "User identity is " + identitydiag.StatusMessage(d.User.Status) + "; bot identity is ready for bot/tenant API calls. Run `lark-cli auth login` to enable user identity."
|
|
case d.User.Status == identitydiag.StatusNeedsRefresh:
|
|
result["note"] = "User identity needs refresh and will be refreshed automatically on the next user API call."
|
|
case !d.User.Available && !d.Bot.Available:
|
|
result["note"] = "No usable identity is available. Configure bot credentials or run `lark-cli auth login`."
|
|
}
|
|
}
|