Skip to content

feat: add user_token_getter_url, people can get user token from app manager#695

Open
wangjingling wants to merge 1 commit intolarksuite:mainfrom
wangjingling:feature/user_access_token_getter
Open

feat: add user_token_getter_url, people can get user token from app manager#695
wangjingling wants to merge 1 commit intolarksuite:mainfrom
wangjingling:feature/user_access_token_getter

Conversation

@wangjingling
Copy link
Copy Markdown

@wangjingling wangjingling commented Apr 28, 2026

Summary

Changes

  • Added --user-token-getter-url configuration option enabling an alternate user authentication flow, Login process now supports token getter URL as an alternative to device authorization flow
  • Support user-token-getter-url for lark config init
  • Remove --new when skill lark-share call lark config init

Test Plan

  • Unit tests pass
  • Manual local verification confirms the lark-cli config init, lark-cli auth, lark-doc *** command works as expected

Related Issues

  • None

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced token getter URL as an alternative authentication method alongside app secret in configuration setup.
    • config init command now accepts --user-token-getter-url flag for flexible authentication options.
    • Interactive configuration flows now support token getter URL input during app initialization.
  • Documentation

    • Simplified configuration initialization documentation examples.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

This PR introduces an alternative OAuth authentication flow through UserTokenGetterUrl, enabling token acquisition via local callback handlers instead of device-flow. It adds HTTP handlers for OAuth exchange, updates configuration to support the new field across init and interactive flows, extends authentication and credential models, and includes comprehensive test coverage.

Changes

Cohort / File(s) Summary
OAuth Handlers
cmd/auth/get_user_token_url.go.demo
New demo package providing UserAuth handler (redirects to Lark auth endpoint with state/scope) and OAuthCallback handler (exchanges code for token, returns HTML page that POSTs payload to local server).
Login Flow
cmd/auth/login.go, cmd/auth/login_test.go
Introduces authLoginViaGetter path when UserTokenGetterUrl is configured; adds fetchTokenViaGetter and openBrowser to spin up loopback server, open getter URL, and capture callback token. Includes new test suite covering success, missing-token, timeout, user-info fallback, and invalid JSON cases.
Configuration Init
cmd/config/init.go, cmd/config/init_interactive.go
Adds UserTokenGetterUrl field to ConfigInitOptions and configInitResult; updates non-interactive mode to accept either --app-secret or --user-token-getter-url; extends interactive flow with new input prompt and validation (requires App ID plus either secret or token URL); updates success JSON output.
Core Config Models
internal/core/config.go, cmd/config/config_test.go
Extends AppConfig and CliConfig with UserTokenGetterUrl field; updates ResolveConfigFromMulti to skip keychain validation when URL is present; adds test documentation comments; updates two test calls to match function signature changes.
Credential Models
internal/credential/types.go
Adds UserTokenGetterUrl field to Account model and updates AccountFromCliConfig and (*Account).ToCliConfig to transfer the field; expands documentation for multiple public types and methods.
Dependencies & Docs
go.mod, skills/lark-shared/SKILL.md
Bumps github.com/davecgh/go-spew to v1.1.2-0.20180830191138; removes --new flag from example config init command in documentation.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Browser as Browser
    participant LocalServer as Local Server<br/>(Loopback)
    participant LarkOAuth as Lark OAuth<br/>Server
    participant CLIApp as CLI App

    User->>CLIApp: Initiate login with UserTokenGetterUrl
    CLIApp->>LocalServer: Start callback listener on port (via state)
    CLIApp->>Browser: Open UserTokenGetterUrl with state=port&scope=...
    Browser->>LarkOAuth: Navigate to Lark auth endpoint
    LarkOAuth->>Browser: User grants permission
    Browser->>LarkOAuth: Redirect with authorization code
    LarkOAuth->>User: Approval complete
    User->>Browser: User navigates to callback URL or auto-redirect occurs
    Browser->>LocalServer: POST callback payload (code, state, data)
    LocalServer->>LocalServer: Parse response JSON
    LocalServer->>LarkOAuth: Exchange code for access_token<br/>(Authen.AccessToken.Create)
    LarkOAuth-->>LocalServer: Return access token & user data
    LocalServer->>Browser: Return HTML page with success message
    Browser->>Browser: Auto-close after 3 seconds
    LocalServer->>CLIApp: Token callback received
    CLIApp->>CLIApp: Store token & profile in config
    CLIApp-->>User: Login complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

size/L

Suggested reviewers

  • liangshuo-1
  • albertnusouo
  • XingjianSun

Poem

🐰 A new path opens wide, through callbacks and OAuth's tide,
Local servers spin and listen, tokens gleam with user's glisten,
Config flows adapt with grace, letting getters find their place,
State and scope dance together, binding all the auth tether! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding user_token_getter_url support and explaining its primary benefit (getting user token from app manager).
Description check ✅ Passed The description includes all required template sections (Summary, Changes, Test Plan, Related Issues) with sufficient detail about the implementation scope and testing performed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label Apr 28, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
cmd/config/init_interactive.go (1)

90-97: Consider clarifying the help text for users.

The title "the url must get token and send token data to http://127.0.0.1:${state}" describes internal implementation details. Users might not understand what ${state} means or how the callback mechanism works.

Consider a more user-friendly description like: "URL that provides OAuth callback integration for token retrieval".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/config/init_interactive.go` around lines 90 - 97, Update the help/title
text for the interactive input created via huh.NewInput() assigned to
userTokenGetterUrlInput so it doesn't expose internal callback details; replace
the current Title("UserTokenGetterUrl (Optional, the url must get token and send
token data to http://127.0.0.1:${state})") with a clearer, user-friendly
description (for example: "UserTokenGetter URL (optional) — URL that provides
OAuth callback integration for token retrieval"), keeping the existing
Placeholder logic that uses firstApp.UserTokenGetterUrl when available.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/auth/get_user_token_url.go.demo`:
- Around line 19-24: The authHost constant is malformed
("open.feishu.cn/open.larksuite.com") causing UserAuth to build an invalid
redirect URL; update the authHost constant to a single valid domain (for example
"open.feishu.cn" or "open.larksuite.com" depending on target region) and ensure
the UserAuth URL construction uses only the authHost constant (refer to the
authHost, appID, redirectURI constants and the UserAuth URL builder) so the
resulting OAuth endpoint is a correct host + path like
"https://<authHost>/open-apis/authen/v1/index".

In `@cmd/auth/login.go`:
- Around line 364-366: The authLoginViaGetter path currently passes finalScope
into fetchTokenViaGetter but then writes an empty Scope, skips
ensureRequestedScopesGranted, and omits the scope summary; update
authLoginViaGetter (and the similar block around lines 421-441) to store the
granted scopes returned by fetchTokenViaGetter into the msg.Scope (or equivalent
field), call ensureRequestedScopesGranted(finalScope, grantedScopes, ...) to
validate the getter didn't narrow permissions, and include the resulting scope
summary in the success reporting/logging so behavior matches the device flow.
- Around line 459-463: The /user_access_token handler currently trusts the first
request based only on the exposed state (port); fix this by generating a
cryptographically-random per-login nonce when starting the local server, include
that nonce as a required query param in the getter URL, and validate that the
incoming callback's nonce matches before sending to tokenCh (the handler for
"/user_access_token" should reject mismatched or missing nonces and not write to
tokenCh). Stop using Access-Control-Allow-Origin: * unconditionally — only set a
restrictive CORS origin (or remove the header) until the callback is
authenticated and the nonce verified. Apply the same nonce verification and CORS
tightening to the other local handlers mentioned in the comment (the other
mux.HandleFunc callbacks around the same flow) so the getter cannot race and
inject tokens.
- Around line 470-480: The handler currently accepts tokens from the URL query
when r.Method != http.MethodPost which leaks credentials; change the logic in
the login handler (the block that sets tokenData based on r.Method) to reject
any non-POST request instead of reading r.URL.Query().Get("token") — return an
HTTP 405 (or 400) with a clear error like "token must be POSTed as JSON" and
only parse tokenData from the POST body (using io.ReadAll or JSON decoding as
appropriate); update any references to tokenData and ensure early return on
non-POST to prevent further processing.

---

Nitpick comments:
In `@cmd/config/init_interactive.go`:
- Around line 90-97: Update the help/title text for the interactive input
created via huh.NewInput() assigned to userTokenGetterUrlInput so it doesn't
expose internal callback details; replace the current Title("UserTokenGetterUrl
(Optional, the url must get token and send token data to
http://127.0.0.1:${state})") with a clearer, user-friendly description (for
example: "UserTokenGetter URL (optional) — URL that provides OAuth callback
integration for token retrieval"), keeping the existing Placeholder logic that
uses firstApp.UserTokenGetterUrl when available.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 320e14ed-217d-45db-bf86-6dcc158b50e2

📥 Commits

Reviewing files that changed from the base of the PR and between b8d0f96 and edf9499.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • cmd/auth/get_user_token_url.go.demo
  • cmd/auth/login.go
  • cmd/auth/login_test.go
  • cmd/config/config_test.go
  • cmd/config/init.go
  • cmd/config/init_interactive.go
  • go.mod
  • internal/core/config.go
  • internal/credential/types.go
  • skills/lark-shared/SKILL.md

Comment on lines +19 to +24
const (
appID = ""
appSecret = ""
redirectURI
authHost = "open.feishu.cn/open.larksuite.com"
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the demo authorization host.

authHost currently concatenates the Feishu and Larksuite domains, so UserAuth builds https://open.feishu.cn/open.larksuite.com/open-apis/authen/v1/index instead of a real OAuth endpoint. Anyone copying this demo will hit a broken redirect immediately.

Suggested fix
 const (
 	appID     = ""
 	appSecret = ""
 	redirectURI
-	authHost = "open.feishu.cn/open.larksuite.com"
+	authHost = "open.feishu.cn" // or "open.larksuite.com", selected from the target brand
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/auth/get_user_token_url.go.demo` around lines 19 - 24, The authHost
constant is malformed ("open.feishu.cn/open.larksuite.com") causing UserAuth to
build an invalid redirect URL; update the authHost constant to a single valid
domain (for example "open.feishu.cn" or "open.larksuite.com" depending on target
region) and ensure the UserAuth URL construction uses only the authHost constant
(refer to the authHost, appID, redirectURI constants and the UserAuth URL
builder) so the resulting OAuth endpoint is a correct host + path like
"https://<authHost>/open-apis/authen/v1/index".

Comment thread cmd/auth/login.go
Comment on lines +364 to +366
func authLoginViaGetter(opts *LoginOptions, config *core.CliConfig, finalScope string, msg *loginMsg, log func(string, ...interface{})) error {
f := opts.Factory
token, err := fetchTokenViaGetter(opts.Ctx, config.UserTokenGetterUrl, finalScope, log)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep scope handling consistent with the device flow.

This path forwards finalScope to the getter, but it stores Scope: "", skips ensureRequestedScopesGranted, and reports success without any scope summary. That means auth login --scope/--domain/--recommend can succeed even if the getter ignored or narrowed the requested permissions, which is a behavior change from the existing device flow.

Also applies to: 421-441

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/auth/login.go` around lines 364 - 366, The authLoginViaGetter path
currently passes finalScope into fetchTokenViaGetter but then writes an empty
Scope, skips ensureRequestedScopesGranted, and omits the scope summary; update
authLoginViaGetter (and the similar block around lines 421-441) to store the
granted scopes returned by fetchTokenViaGetter into the msg.Scope (or equivalent
field), call ensureRequestedScopesGranted(finalScope, grantedScopes, ...) to
validate the getter didn't narrow permissions, and include the resulting scope
summary in the success reporting/logging so behavior matches the device flow.

Comment thread cmd/auth/login.go
Comment on lines +459 to +463
mux := http.NewServeMux()
mux.HandleFunc("/user_access_token", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Authenticate the loopback callback before accepting a token.

The localhost handler trusts the first request that reaches /user_access_token, and the only value shared with the getter is the listening port (state). Because that port is exposed in the getter URL and never verified on callback, any page that learns it can race the real callback and inject arbitrary token JSON into the CLI. Please add a per-login nonce and require the same nonce on the callback before writing to tokenCh; the permissive Access-Control-Allow-Origin: * should also go away once the callback is authenticated.

Also applies to: 498-500, 525-529

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/auth/login.go` around lines 459 - 463, The /user_access_token handler
currently trusts the first request based only on the exposed state (port); fix
this by generating a cryptographically-random per-login nonce when starting the
local server, include that nonce as a required query param in the getter URL,
and validate that the incoming callback's nonce matches before sending to
tokenCh (the handler for "/user_access_token" should reject mismatched or
missing nonces and not write to tokenCh). Stop using
Access-Control-Allow-Origin: * unconditionally — only set a restrictive CORS
origin (or remove the header) until the callback is authenticated and the nonce
verified. Apply the same nonce verification and CORS tightening to the other
local handlers mentioned in the comment (the other mux.HandleFunc callbacks
around the same flow) so the getter cannot race and inject tokens.

Comment thread cmd/auth/login.go
Comment on lines +470 to +480
var tokenData string
if r.Method == http.MethodPost {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
tokenData = string(body)
} else {
tokenData = r.URL.Query().Get("token")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject token delivery through the query string.

The GET ?token= fallback leaks access tokens into browser history, proxy logs, and crash reports. The bundled demo already uses a JSON POST, so non-POST requests should be rejected instead of reading r.URL.Query().Get("token").

Suggested fix
-		var tokenData string
-		if r.Method == http.MethodPost {
-			body, err := io.ReadAll(r.Body)
-			if err != nil {
-				http.Error(w, "failed to read body", http.StatusBadRequest)
-				return
-			}
-			tokenData = string(body)
-		} else {
-			tokenData = r.URL.Query().Get("token")
-		}
+		if r.Method != http.MethodPost {
+			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+			return
+		}
+		body, err := io.ReadAll(r.Body)
+		if err != nil {
+			http.Error(w, "failed to read body", http.StatusBadRequest)
+			return
+		}
+		tokenData := string(body)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var tokenData string
if r.Method == http.MethodPost {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
tokenData = string(body)
} else {
tokenData = r.URL.Query().Get("token")
}
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
tokenData := string(body)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/auth/login.go` around lines 470 - 480, The handler currently accepts
tokens from the URL query when r.Method != http.MethodPost which leaks
credentials; change the logic in the login handler (the block that sets
tokenData based on r.Method) to reject any non-POST request instead of reading
r.URL.Query().Get("token") — return an HTTP 405 (or 400) with a clear error like
"token must be POSTed as JSON" and only parse tokenData from the POST body
(using io.ReadAll or JSON decoding as appropriate); update any references to
tokenData and ensure early return on non-POST to prevent further processing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants