-
Notifications
You must be signed in to change notification settings - Fork 604
feat: app administrator provides an auth page, allowing others to obtain the user_access_token without requiring the app_secret #634
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| package auth | ||
|
|
||
| // this file is a demo for user_token_getter_url | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "net/http" | ||
| "net/url" | ||
| "strconv" | ||
|
|
||
| "github.com/cloudwego/hertz/pkg/app" | ||
| "github.com/cloudwego/hertz/pkg/common/utils" | ||
| lark "github.com/larksuite/oapi-sdk-go/v3" | ||
| larkauthen "github.com/larksuite/oapi-sdk-go/v3/service/authen/v1" | ||
| ) | ||
|
|
||
| var ( | ||
| appID = "app_id" | ||
| larkClient = lark.NewClient(appID, "app_secret") | ||
| redirectURI = "redirect_uri" | ||
| ) | ||
|
|
||
| // UserAuth handles the initial step of the OAuth flow by redirecting the user to the Lark authorization page. | ||
| func UserAuth(ctx context.Context, c *app.RequestContext) { | ||
| state := c.Query("state") | ||
| if state == "" { | ||
| state = "8888" | ||
| } | ||
|
|
||
| scope := c.Query("scope") | ||
|
|
||
| authURLObj, _ := url.Parse("https://open.feishu.cn/open-apis/authen/v1/index") | ||
| query := authURLObj.Query() | ||
| query.Set("redirect_uri", redirectURI) | ||
| query.Set("app_id", appID) | ||
| query.Set("state", state) | ||
| if scope != "" { | ||
| query.Set("scope", scope) | ||
| } | ||
| authURLObj.RawQuery = query.Encode() | ||
|
|
||
| c.Redirect(http.StatusFound, []byte(authURLObj.String())) | ||
| } | ||
|
|
||
| // OAuthCallback processes the OAuth callback from Lark, fetches the user access token, and sends it back to the local server. | ||
| func OAuthCallback(ctx context.Context, c *app.RequestContext) { //ignore_security_alert IDOR | ||
| code := c.Query("code") | ||
| state := c.Query("state") | ||
| if state == "" { | ||
| state = "8888" | ||
| } | ||
|
|
||
| if code == "" { | ||
| c.JSON(http.StatusBadRequest, utils.H{"error": "missing code"}) | ||
| return | ||
| } | ||
|
|
||
| stateInt, err := strconv.Atoi(state) | ||
| if err != nil { | ||
| c.JSON(http.StatusBadRequest, utils.H{"error": "invalid state parameter, must be integer"}) | ||
| return | ||
| } | ||
|
Comment on lines
+48
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate
Proposed fix stateInt, err := strconv.Atoi(state)
- if err != nil {
- c.JSON(http.StatusBadRequest, utils.H{"error": "invalid state parameter, must be integer"})
- return
- }
+ if err != nil || stateInt < 1 || stateInt > 65535 {
+ c.JSON(http.StatusBadRequest, utils.H{"error": "invalid state parameter, must be a TCP port in [1,65535]"})
+ return
+ }🤖 Prompt for AI Agents |
||
|
|
||
| // get user_access_token | ||
| req := larkauthen.NewCreateAccessTokenReqBuilder(). | ||
| Body(larkauthen.NewCreateAccessTokenReqBodyBuilder(). | ||
| GrantType("authorization_code"). | ||
| Code(code). | ||
| Build()). | ||
| Build() | ||
|
|
||
| resp, err := larkClient.Authen.AccessToken.Create(ctx, req) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, utils.H{"error": "failed to get access token", "detail": err.Error()}) | ||
| return | ||
| } | ||
|
|
||
| if !resp.Success() { | ||
| c.JSON(http.StatusInternalServerError, utils.H{"error": "feishu API returned error", "code": resp.Code, "msg": resp.Msg}) | ||
| return | ||
| } | ||
|
|
||
| data := resp.Data | ||
| if data == nil || data.AccessToken == nil { | ||
| c.JSON(http.StatusInternalServerError, utils.H{"error": "empty data in response"}) | ||
| return | ||
| } | ||
|
|
||
| // TODO check user permission or scope | ||
|
|
||
| dataBytes, err := json.Marshal(data) | ||
| if err != nil { | ||
| c.JSON(http.StatusInternalServerError, utils.H{"error": "failed to marshal data"}) | ||
| return | ||
| } | ||
|
|
||
| html := fmt.Sprintf(`<!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <title>Lark Token</title> | ||
| </head> | ||
| <body> | ||
| <p>Sending token...</p> | ||
| <script> | ||
| window.onload = function() { | ||
| var url = 'http://127.0.0.1:%d/user_access_token'; | ||
| var data = %s; | ||
| fetch(url, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json' | ||
| }, | ||
| body: JSON.stringify(data) | ||
| }).then(function(response) { | ||
| document.body.innerHTML += '<p>Token sended,close in 3 seconds...</p>'; | ||
| setTimeout(function() { | ||
| window.close(); | ||
| }, 3000); | ||
| }).catch(function(err) { | ||
| document.body.innerHTML += '<p>Token send fail: ' + err + '</p>'; | ||
| }); | ||
| }; | ||
| </script> | ||
| </body> | ||
| </html>`, stateInt, string(dataBytes)) | ||
|
|
||
| c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(html)) | ||
|
Comment on lines
+99
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interpolating JSON into an inline
Minimal safer interpolation- var data = %s;
+ var data = JSON.parse(%q);
...
-`, stateInt, string(dataBytes))
+`, stateInt, string(dataBytes))(passing via 🤖 Prompt for AI Agents |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: larksuite/cli
Length of output: 760
🏁 Script executed:
# First, read the full demo file to confirm its structure and purpose cat -n cmd/auth/get_user_token_url.demo_test.goRepository: larksuite/cli
Length of output: 4294
🏁 Script executed:
Repository: larksuite/cli
Length of output: 39
🏁 Script executed:
Repository: larksuite/cli
Length of output: 89
🏁 Script executed:
Repository: larksuite/cli
Length of output: 206
🏁 Script executed:
Repository: larksuite/cli
Length of output: 248
🏁 Script executed:
Repository: larksuite/cli
Length of output: 194
🏁 Script executed:
Repository: larksuite/cli
Length of output: 39
🏁 Script executed:
Repository: larksuite/cli
Length of output: 518
🏁 Script executed:
Repository: larksuite/cli
Length of output: 674
🏁 Script executed:
Repository: larksuite/cli
Length of output: 75
🏁 Script executed:
Repository: larksuite/cli
Length of output: 1148
🏁 Script executed:
Repository: larksuite/cli
Length of output: 2168
🏁 Script executed:
Repository: larksuite/cli
Length of output: 1614
🏁 Script executed:
Repository: larksuite/cli
Length of output: 71
🏁 Script executed:
Repository: larksuite/cli
Length of output: 1250
🏁 Script executed:
Repository: larksuite/cli
Length of output: 86
Rename or relocate this file — it is a demo, not a test.
The file is named
*_test.gobut contains noTest*,Benchmark*, orExample*functions, and the header explicitly identifies it as "a demo for user_token_getter_url." Go's test runner will include it in the test binary but never execute anything from it. The exportedUserAuthandOAuthCallbackhandlers are reference implementations showing how a custom getter URL should implement the OAuth flow (as consumed byfetchTokenViaGetterinlogin.go), not test code.Move this to an
examples/directory (e.g.,examples/user_token_getter/main.go) as a standalonemainpackage with proper documentation, or rename it without the_test.gosuffix and add a//go:build demotag to prevent it from bloating the default binary.Additionally, per the coding guidelines requiring tests for every behavior change, the
--user-token-getter-urlfeature inlogin.golacks any unit or integration tests. The demo file cannot serve as a substitute — add tests that exercise the feature's actual code paths.🤖 Prompt for AI Agents