Skip to content

Commit c8b9809

Browse files
Revert "feat(auth): add QR code support for device auth flow (#942)" (#950)
This reverts commit 7af616b.
1 parent de00343 commit c8b9809

2 files changed

Lines changed: 29 additions & 89 deletions

File tree

cmd/auth/login.go

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@ package auth
55

66
import (
77
"context"
8-
"encoding/base64"
98
"encoding/json"
109
"fmt"
1110
"sort"
1211
"strings"
1312
"time"
1413

15-
qrcode "github.com/skip2/go-qrcode"
1614
"github.com/spf13/cobra"
1715

1816
larkauth "github.com/larksuite/cli/internal/auth"
@@ -267,15 +265,11 @@ func authLoginRun(opts *LoginOptions) error {
267265
if err := saveLoginRequestedScope(authResp.DeviceCode, finalScope); err != nil {
268266
fmt.Fprintf(f.IOStreams.ErrOut, "[lark-cli] [WARN] auth login: failed to cache requested scopes: %v\n", err)
269267
}
270-
qrCodeASCII, qrCodeBase64 := generateQRCode(authResp.VerificationUriComplete)
271268
data := map[string]interface{}{
272-
"qr_code_ascii": qrCodeASCII,
273-
"qr_code_base64": qrCodeBase64,
274-
"qr_code_display_hint": msg.QRCodeDisplayHint,
275-
"verification_url": authResp.VerificationUriComplete,
276-
"device_code": authResp.DeviceCode,
277-
"expires_in": authResp.ExpiresIn,
278-
"hint": fmt.Sprintf("Show qr_code_base64 as an image and verification_url exactly as returned by the CLI to the user. If your agent cannot display images, show qr_code_ascii (ASCII QR code) or verification_url instead. Treat verification_url as an opaque string: Do not URL-encode or decode it, do not normalize, rewrite, do not add %%20, spaces, or punctuation, do not wrap it as Markdown link text; prefer a fenced code block containing only the raw URL. For agent harnesses that only deliver final turn messages, make the image (if displayable) and verification_url the final message of the turn and return control to the user; do not block on --device-code in the same turn. After the user confirms authorization in a later step, run: lark-cli auth login --device-code %s", authResp.DeviceCode),
269+
"verification_url": authResp.VerificationUriComplete,
270+
"device_code": authResp.DeviceCode,
271+
"expires_in": authResp.ExpiresIn,
272+
"hint": fmt.Sprintf("Show verification_url to the user exactly as returned by the CLI and treat it as an opaque string. Do not URL-encode or decode it, do not normalize or rewrite it, do not add %%20, spaces, or punctuation, and do not wrap it as Markdown link text; prefer a fenced code block containing only the raw URL. For agent harnesses that only deliver final turn messages, make the URL the final message of the turn and return control to the user; do not block on --device-code in the same turn. After the user confirms authorization in a later step, run: lark-cli auth login --device-code %s", authResp.DeviceCode),
279273
}
280274
encoder := json.NewEncoder(f.IOStreams.Out)
281275
encoder.SetEscapeHTML(false)
@@ -291,12 +285,8 @@ func authLoginRun(opts *LoginOptions) error {
291285
// stdout into a JSON parser sees it without stream-mixing surprises),
292286
// text mode prints to stderr (alongside the URL prompt).
293287
if opts.JSON {
294-
qrCodeASCII, qrCodeBase64 := generateQRCode(authResp.VerificationUriComplete)
295288
data := map[string]interface{}{
296289
"event": "device_authorization",
297-
"qr_code_ascii": qrCodeASCII,
298-
"qr_code_base64": qrCodeBase64,
299-
"qr_code_display_hint": msg.QRCodeDisplayHint,
300290
"verification_uri": authResp.VerificationUri,
301291
"verification_uri_complete": authResp.VerificationUriComplete,
302292
"user_code": authResp.UserCode,
@@ -309,21 +299,7 @@ func authLoginRun(opts *LoginOptions) error {
309299
return output.Errorf(output.ExitInternal, "internal", "failed to write JSON output: %v", err)
310300
}
311301
} else {
312-
// Branch on TTY: human-friendly copy in interactive terminals,
313-
// For non-TTY (AI agent callers), output text with both ASCII and base64 QR code.
314-
fmt.Fprintf(f.IOStreams.ErrOut, msg.ScanQRCode)
315-
qrCodeASCII, qrCodeBase64 := generateQRCode(authResp.VerificationUriComplete)
316-
fmt.Fprint(f.IOStreams.ErrOut, qrCodeASCII)
317-
if !f.IOStreams.IsTerminal {
318-
if qrCodeBase64 != "" {
319-
fmt.Fprintf(f.IOStreams.ErrOut, "[BASE64 QR CODE START]\n")
320-
fmt.Fprintf(f.IOStreams.ErrOut, "%s\n", qrCodeBase64)
321-
fmt.Fprintf(f.IOStreams.ErrOut, "[BASE64 QR CODE END]\n")
322-
fmt.Fprintf(f.IOStreams.ErrOut, "%s\n", msg.QRCodeDisplayHint)
323-
}
324-
}
325-
fmt.Fprintln(f.IOStreams.ErrOut)
326-
fmt.Fprintf(f.IOStreams.ErrOut, msg.ScanOrOpenLink)
302+
fmt.Fprintf(f.IOStreams.ErrOut, msg.OpenURL)
327303
fmt.Fprintf(f.IOStreams.ErrOut, " %s\n\n", authResp.VerificationUriComplete)
328304
fmt.Fprintln(f.IOStreams.ErrOut, msg.AgentTimeoutHint)
329305
}
@@ -476,8 +452,6 @@ func authLoginPollDeviceCode(opts *LoginOptions, config *core.CliConfig, msg *lo
476452
return nil
477453
}
478454

479-
// syncLoginUserToProfile updates the profile's user list to contain only the newly
480-
// authenticated user, removing any previously stored tokens for other users.
481455
func syncLoginUserToProfile(profileName, appID, openID, userName string) error {
482456
multi, err := core.LoadMultiAppConfig()
483457
if err != nil {
@@ -503,7 +477,6 @@ func syncLoginUserToProfile(profileName, appID, openID, userName string) error {
503477
return nil
504478
}
505479

506-
// findProfileByName locates an AppConfig by profile name from the multi-app configuration.
507480
func findProfileByName(multi *core.MultiAppConfig, profileName string) *core.AppConfig {
508481
for i := range multi.Apps {
509482
if multi.Apps[i].ProfileName() == profileName {
@@ -695,15 +668,3 @@ func applyExcludeScopes(requested string, excludes []string) (string, []string)
695668
}
696669
return joinSortedScopeSet(kept), nil
697670
}
698-
699-
// generateQRCode creates both ASCII art and base64-encoded PNG versions of a QR code
700-
// for the given verification URL. Returns empty strings if generation fails.
701-
func generateQRCode(verificationURL string) (ascii string, base64Str string) {
702-
if qr, err := qrcode.New(verificationURL, qrcode.Medium); err == nil {
703-
ascii = qr.ToSmallString(true)
704-
if pngBytes, err := qr.PNG(256); err == nil {
705-
base64Str = base64.StdEncoding.EncodeToString(pngBytes)
706-
}
707-
}
708-
return
709-
}

cmd/auth/login_messages.go

Lines changed: 24 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ type loginMsg struct {
2323
OpenURL string
2424
WaitingAuth string
2525
AgentTimeoutHint string
26-
AgentNoJSONHint string
2726
AuthSuccess string
2827
LoginSuccess string
2928
AuthorizedUser string
@@ -33,12 +32,6 @@ type loginMsg struct {
3332
NewlyGrantedScopes string
3433
NoScopes string
3534
StatusHint string
36-
// QR code messages
37-
ScanQRCode string // header shown above QR code (TTY)
38-
ScanOrOpenLink string // post-QR alt link prompt ("or open...") (TTY)
39-
OpenLinkNonTTY string // primary link prompt (non-TTY/AI)
40-
WaitingForScanNonTTY string // passive waiting indicator (non-TTY/AI)
41-
QRCodeDisplayHint string // hint for displaying qr_code_base64
4235

4336
// Non-interactive hint (no flags)
4437
HintHeader string
@@ -64,24 +57,18 @@ var loginMsgZh = &loginMsg{
6457
ErrNoDomain: "请至少选择一个业务域",
6558
ConfirmAuth: "确认授权?",
6659

67-
OpenURL: "在浏览器中打开以下链接进行认证:\n\n",
68-
WaitingAuth: "等待用户授权...",
69-
AgentTimeoutHint: "[AI agent] 此命令最长阻塞约 10 分钟,等待用户在浏览器内完成授权。请确保 runner 的 timeout >= 600s。若你的 harness 只会把最终回复发给用户,请改用 `lark-cli auth login --no-wait --json` 拿到 device_code、qr_code_base64 和 verification_url,把二维码图片(解码 qr_code_base64)和 verification_url 作为本轮最终消息发给用户并结束本轮;等用户回复已完成授权后,再在后续步骤运行 `lark-cli auth login --device-code <code>` 续上轮询。**不要在同一轮里展示 URL 后立刻阻塞执行 --device-code**,也不要短 timeout 反复重试;每次重启会作废上一轮的 device code,导致用户授权链接失效。若你的 agent 无法显示图片,只展示 verification_url 即可。向用户展示授权链接时,必须逐字原样转发 CLI 返回的 URL,把它视为不可修改的 opaque string;不要做 URL 编码或解码,不要补 `%20`、空格或标点,不要改写成 Markdown 链接,建议用只包含该 URL 的代码块单独输出。",
70-
AgentNoJSONHint: "[AI agent] 如需获取二维码图片,请添加 --json 或使用 `lark-cli auth login --no-wait --json` 获取 qr_code_base64 字段。",
71-
AuthSuccess: "已收到授权确认,正在获取用户信息并校验授权结果...",
72-
LoginSuccess: "授权成功! 用户: %s (%s)",
73-
AuthorizedUser: "当前授权账号: %s (%s)",
74-
ScopeMismatch: "授权结果异常: 以下请求 scopes 未被授予: %s",
75-
ScopeHint: "以上结果是本次授权请求用户最终确认后的结果,请勿持续重试;Scopes 未授予的原因是多样的,如 scope 被禁用;具体原因已通过授权页提示用户。可执行 `lark-cli auth status` 查看账号当前已授予的全部 scopes;",
76-
RequestedScopes: " 本次请求 scopes: %s\n",
77-
NewlyGrantedScopes: " 本次新授予 scopes: %s\n",
78-
NoScopes: "(空)",
79-
StatusHint: "可执行 `lark-cli auth status` 查看账号当前已授予的全部 scopes;",
80-
ScanQRCode: "\n使用飞书 / Lark 扫码授权:\n\n",
81-
ScanOrOpenLink: "\n或打开以下链接完成授权:\n",
82-
OpenLinkNonTTY: "\n打开以下链接授权:\n\n",
83-
WaitingForScanNonTTY: "等待授权...",
84-
QRCodeDisplayHint: "[AI Agent] 请尽可能展示 qr_code_base64 图片!qr_code_base64 包含 base64 编码的 PNG 图片,解码后以图片形式显示(256x256 像素)便于用户扫码授权。如果 agent 无法显示图片,可以展示 qr_code_ascii(ASCII 二维码),并且展示 verification_url 让用户可以手动打开。",
60+
OpenURL: "在浏览器中打开以下链接进行认证:\n\n",
61+
WaitingAuth: "等待用户授权...",
62+
AgentTimeoutHint: "[AI agent] 此命令最长阻塞约 10 分钟,等待用户在浏览器内完成授权。请确保 runner 的 timeout >= 600s。若你的 harness 只会把最终回复发给用户,请改用 `lark-cli auth login --no-wait --json` 拿到 device_code 和 verification_url,把 verification_url 作为本轮最终消息原样发给用户并结束本轮;等用户回复已完成授权后,再在后续步骤运行 `lark-cli auth login --device-code <code>` 续上轮询。**不要在同一轮里展示 URL 后立刻阻塞执行 --device-code**,也不要短 timeout 反复重试;每次重启会作废上一轮的 device code,导致用户授权链接失效。向用户展示授权链接时,必须逐字原样转发 CLI 返回的 URL,把它视为不可修改的 opaque string;不要做 URL 编码或解码,不要补 `%20`、空格或标点,不要改写成 Markdown 链接,建议用只包含该 URL 的代码块单独输出。",
63+
AuthSuccess: "已收到授权确认,正在获取用户信息并校验授权结果...",
64+
LoginSuccess: "授权成功! 用户: %s (%s)",
65+
AuthorizedUser: "当前授权账号: %s (%s)",
66+
ScopeMismatch: "授权结果异常: 以下请求 scopes 未被授予: %s",
67+
ScopeHint: "以上结果是本次授权请求用户最终确认后的结果,请勿持续重试;Scopes 未授予的原因是多样的,如 scope 被禁用;具体原因已通过授权页提示用户。可执行 `lark-cli auth status` 查看账号当前已授予的全部 scopes;",
68+
RequestedScopes: " 本次请求 scopes: %s\n",
69+
NewlyGrantedScopes: " 本次新授予 scopes: %s\n",
70+
NoScopes: "(空)",
71+
StatusHint: "可执行 `lark-cli auth status` 查看账号当前已授予的全部 scopes;",
8572

8673
HintHeader: "请指定要授权的权限:\n",
8774
HintCommon1: " --recommend 授权推荐权限",
@@ -106,24 +93,18 @@ var loginMsgEn = &loginMsg{
10693
ErrNoDomain: "please select at least one domain",
10794
ConfirmAuth: "Confirm authorization?",
10895

109-
OpenURL: "Open this URL in your browser to authenticate:\n\n",
110-
WaitingAuth: "Waiting for user authorization...",
111-
AgentTimeoutHint: "[AI agent] This command blocks for up to ~10 minutes while waiting for the user to authorize in their browser. Make sure your runner's timeout is >= 600s. If your harness only delivers final turn messages, use `lark-cli auth login --no-wait --json` to get device_code, qr_code_base64 and verification_url, present the QR code image (decode qr_code_base64) and verification_url to the user as the final message of this turn, then end the turn; after the user replies that they authorized, run `lark-cli auth login --device-code <code>` in a later step to resume polling. **Do NOT show the URL and then immediately block on --device-code in the same turn**, and do not retry with a short timeout; each restart invalidates the previous device code and makes the earlier authorization URL useless. If your agent cannot display images, show verification_url only. When showing the authorization URL to the user, copy the CLI-returned URL exactly as-is and treat it as an opaque string. Do not URL-encode or decode it, do not add `%20`, spaces, or punctuation, do not rewrite it as Markdown link text, and prefer a fenced code block containing only the raw URL.",
112-
AgentNoJSONHint: "[AI agent] To get QR code image, add --json or use `lark-cli auth login --no-wait --json` to get the qr_code_base64 field.",
113-
AuthSuccess: "Authorization confirmed, fetching user info and validating granted scopes...",
114-
LoginSuccess: "Authorization successful! User: %s (%s)",
115-
AuthorizedUser: "Authorized account: %s (%s)",
116-
ScopeMismatch: "authorization result is abnormal: these requested scopes were not granted: %s",
117-
ScopeHint: "The result above is the user's final confirmation for this authorization request. Do not retry continuously. Scopes may be not granted for various reasons, such as a scope being disabled. The specific reason has already been shown to the user on the authorization page. Run `lark-cli auth status` to inspect all scopes currently granted to the account.",
118-
RequestedScopes: " Requested scopes: %s\n",
119-
NewlyGrantedScopes: " Newly granted scopes: %s\n",
120-
NoScopes: "(none)",
121-
StatusHint: "Run `lark-cli auth status` to inspect all scopes currently granted to the account.",
122-
ScanQRCode: "\nScan the QR code with Feishu/Lark to authorize:\n\n",
123-
ScanOrOpenLink: "\nOr open the link below to complete authorization:\n",
124-
OpenLinkNonTTY: "\nOpen the link below to authorize:\n\n",
125-
WaitingForScanNonTTY: "Waiting for authorization...",
126-
QRCodeDisplayHint: "[AI Agent] Please display qr_code_base64 image if at all possible! qr_code_base64 contains a base64-encoded PNG image. Decode it and display as an image (256x256 pixels) for easy QR code scanning. If your agent cannot display images, you can show qr_code_ascii (ASCII QR code) and display verification_url for users to open manually.",
96+
OpenURL: "Open this URL in your browser to authenticate:\n\n",
97+
WaitingAuth: "Waiting for user authorization...",
98+
AgentTimeoutHint: "[AI agent] This command blocks for up to ~10 minutes while waiting for the user to authorize in their browser. Make sure your runner's timeout is >= 600s. If your harness only delivers final turn messages, use `lark-cli auth login --no-wait --json` to get device_code and verification_url, present verification_url to the user exactly as the final message of this turn, then end the turn; after the user replies that they authorized, run `lark-cli auth login --device-code <code>` in a later step to resume polling. **Do NOT show the URL and then immediately block on --device-code in the same turn**, and do not retry with a short timeout; each restart invalidates the previous device code and makes the earlier authorization URL useless. When showing the authorization URL to the user, copy the CLI-returned URL exactly as-is and treat it as an opaque string. Do not URL-encode or decode it, do not add `%20`, spaces, or punctuation, do not rewrite it as Markdown link text, and prefer a fenced code block containing only the raw URL.",
99+
AuthSuccess: "Authorization confirmed, fetching user info and validating granted scopes...",
100+
LoginSuccess: "Authorization successful! User: %s (%s)",
101+
AuthorizedUser: "Authorized account: %s (%s)",
102+
ScopeMismatch: "authorization result is abnormal: these requested scopes were not granted: %s",
103+
ScopeHint: "The result above is the user's final confirmation for this authorization request. Do not retry continuously. Scopes may be not granted for various reasons, such as a scope being disabled. The specific reason has already been shown to the user on the authorization page. Run `lark-cli auth status` to inspect all scopes currently granted to the account.",
104+
RequestedScopes: " Requested scopes: %s\n",
105+
NewlyGrantedScopes: " Newly granted scopes: %s\n",
106+
NoScopes: "(none)",
107+
StatusHint: "Run `lark-cli auth status` to inspect all scopes currently granted to the account.",
127108

128109
HintHeader: "Please specify the scopes to authorize:\n",
129110
HintCommon1: " --recommend authorize recommended scopes",
@@ -133,8 +114,6 @@ var loginMsgEn = &loginMsg{
133114
HintFooter: " lark-cli auth login --help",
134115
}
135116

136-
// getLoginMsg returns the login message bundle for the specified language.
137-
// Supports "zh" for Chinese and "en" for English. Defaults to Chinese.
138117
func getLoginMsg(lang string) *loginMsg {
139118
if lang == "en" {
140119
return loginMsgEn

0 commit comments

Comments
 (0)