From c0c94342a1e10d73cc1a9d9d3f91e9aebc125e52 Mon Sep 17 00:00:00 2001 From: "guoyao.211" Date: Tue, 12 May 2026 14:23:41 +0800 Subject: [PATCH] fix: restrict user_mailbox shortcuts to user identity only user_mailbox.* APIs do not support bot (TAT) access. Six shortcuts incorrectly declared AuthTypes ["user","bot"], causing the Lark API to return a misleading 'param is invalid' error when bot identity was used. Fix by restricting AuthTypes to ["user"] so CheckIdentity rejects bot identity before any API call is made. --- shortcuts/mail/mail_auth_types_test.go | 74 ++++++++++++++++++++++++++ shortcuts/mail/mail_message.go | 2 +- shortcuts/mail/mail_messages.go | 2 +- shortcuts/mail/mail_template_create.go | 2 +- shortcuts/mail/mail_template_update.go | 2 +- shortcuts/mail/mail_thread.go | 2 +- shortcuts/mail/mail_triage.go | 2 +- 7 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 shortcuts/mail/mail_auth_types_test.go diff --git a/shortcuts/mail/mail_auth_types_test.go b/shortcuts/mail/mail_auth_types_test.go new file mode 100644 index 000000000..9112581c6 --- /dev/null +++ b/shortcuts/mail/mail_auth_types_test.go @@ -0,0 +1,74 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package mail + +import ( + "strings" + "testing" + + "github.com/larksuite/cli/internal/cmdutil" + "github.com/larksuite/cli/internal/core" + "github.com/larksuite/cli/shortcuts/common" + "github.com/spf13/cobra" +) + +// TestUserMailboxShortcutsRequireUserIdentity verifies that shortcuts backed +// exclusively by user_mailbox.* APIs reject bot identity before making any +// API call. The error must contain "not supported" so the caller knows the +// issue is identity, not a bad parameter. +func TestUserMailboxShortcutsRequireUserIdentity(t *testing.T) { + tests := []struct { + shortcut common.Shortcut + args []string // minimum args to pass cobra's required-flag check + }{ + { + shortcut: MailTriage, + args: []string{"+triage", "--as", "bot"}, + }, + { + shortcut: MailMessages, + args: []string{"+messages", "--as", "bot", "--message-ids", "dummy"}, + }, + { + shortcut: MailTemplateCreate, + args: []string{"+template-create", "--as", "bot", "--name", "dummy"}, + }, + { + shortcut: MailThread, + args: []string{"+thread", "--as", "bot", "--thread-id", "dummy"}, + }, + { + shortcut: MailTemplateUpdate, + args: []string{"+template-update", "--as", "bot"}, + }, + { + shortcut: MailMessage, + args: []string{"+message", "--as", "bot", "--message-id", "dummy"}, + }, + } + + for _, tt := range tests { + t.Run(tt.shortcut.Command, func(t *testing.T) { + f, _, _, _ := cmdutil.TestFactory(t, &core.CliConfig{ + AppID: "test-app", + AppSecret: "test-secret", + Brand: core.BrandFeishu, + }) + + parent := &cobra.Command{Use: "mail"} + tt.shortcut.Mount(parent, f) + parent.SetArgs(tt.args) + parent.SilenceErrors = true + parent.SilenceUsage = true + + err := parent.Execute() + if err == nil { + t.Fatal("expected error for bot identity, got nil") + } + if !strings.Contains(err.Error(), "not supported") { + t.Errorf("expected 'not supported' in error, got: %v", err) + } + }) + } +} diff --git a/shortcuts/mail/mail_message.go b/shortcuts/mail/mail_message.go index 86fabb51e..b18ec2de5 100644 --- a/shortcuts/mail/mail_message.go +++ b/shortcuts/mail/mail_message.go @@ -18,7 +18,7 @@ var MailMessage = common.Shortcut{ Description: "Use when reading full content for a single email by message ID. Returns normalized body content plus attachments metadata, including inline images.", Risk: "read", Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, - AuthTypes: []string{"user", "bot"}, + AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "mailbox", Default: "me", Desc: "email address (default: me)"}, diff --git a/shortcuts/mail/mail_messages.go b/shortcuts/mail/mail_messages.go index 444aa105f..c9532feb2 100644 --- a/shortcuts/mail/mail_messages.go +++ b/shortcuts/mail/mail_messages.go @@ -26,7 +26,7 @@ var MailMessages = common.Shortcut{ Description: "Use when reading full content for multiple emails by message ID. Prefer this shortcut over calling raw mail user_mailbox.messages batch_get directly, because it base64url-decodes body fields and returns normalized per-message output that is easier to consume.", Risk: "read", Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, - AuthTypes: []string{"user", "bot"}, + AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "mailbox", Default: "me", Desc: "email address (default: me)"}, diff --git a/shortcuts/mail/mail_template_create.go b/shortcuts/mail/mail_template_create.go index 29a8978b4..8f18329b2 100644 --- a/shortcuts/mail/mail_template_create.go +++ b/shortcuts/mail/mail_template_create.go @@ -19,7 +19,7 @@ var MailTemplateCreate = common.Shortcut{ Description: "Create a personal mail template. Scans HTML local paths (reusing draft inline-image detection), uploads inline images and non-inline attachments to Drive, rewrites HTML to cid: references, and POSTs a Template payload to mail.user_mailbox.templates.create.", Risk: "write", Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"}, - AuthTypes: []string{"user", "bot"}, + AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "mailbox", Desc: "Mailbox email address that owns the template (default: me)."}, diff --git a/shortcuts/mail/mail_template_update.go b/shortcuts/mail/mail_template_update.go index d5f68ce2c..dd54bce7c 100644 --- a/shortcuts/mail/mail_template_update.go +++ b/shortcuts/mail/mail_template_update.go @@ -20,7 +20,7 @@ var MailTemplateUpdate = common.Shortcut{ Description: "Update an existing mail template. Supports --inspect (read-only projection), --print-patch-template (prints a JSON skeleton for --patch-file), and flat flags (--set-subject / --set-name / etc). Internally it GETs the template, applies the patch, rewrites local paths to cid: refs, and PUTs a full-replace update (no optimistic locking: last-write-wins).", Risk: "write", Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"}, - AuthTypes: []string{"user", "bot"}, + AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "mailbox", Desc: "Mailbox email address that owns the template (default: me)."}, diff --git a/shortcuts/mail/mail_thread.go b/shortcuts/mail/mail_thread.go index 67d3fbebd..56c2d4ff6 100644 --- a/shortcuts/mail/mail_thread.go +++ b/shortcuts/mail/mail_thread.go @@ -49,7 +49,7 @@ var MailThread = common.Shortcut{ Description: "Use when querying a full mail conversation/thread by thread ID. Returns all messages in chronological order, including replies and drafts, with body content and attachments metadata, including inline images.", Risk: "read", Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, - AuthTypes: []string{"user", "bot"}, + AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "mailbox", Default: "me", Desc: "email address (default: me)"}, diff --git a/shortcuts/mail/mail_triage.go b/shortcuts/mail/mail_triage.go index 1274c5f11..b056633c6 100644 --- a/shortcuts/mail/mail_triage.go +++ b/shortcuts/mail/mail_triage.go @@ -52,7 +52,7 @@ var MailTriage = common.Shortcut{ Description: `List mail summaries (date/from/subject/message_id). Use --query for full-text search, --filter for exact-match conditions.`, Risk: "read", Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"}, - AuthTypes: []string{"user", "bot"}, + AuthTypes: []string{"user"}, Flags: []common.Flag{ {Name: "format", Default: "table", Desc: "output format: table | json | data (json/data output object with pagination fields)"}, {Name: "max", Type: "int", Default: "20", Desc: "maximum number of messages to fetch (1-400; auto-paginates internally)"},