- Introduce a new POST endpoint for verifying integration credentials at /robots/integrations/verify. - Ensure the endpoint is positioned correctly in the routing to avoid conflicts with existing routes. - Enhance the API's capabilities for integration management by allowing credential verification.
179 lines
5 KiB
Go
179 lines
5 KiB
Go
package robot
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
|
|
"github.com/yaoapp/yao/integrations/dingtalk"
|
|
"github.com/yaoapp/yao/integrations/discord"
|
|
"github.com/yaoapp/yao/integrations/feishu"
|
|
"github.com/yaoapp/yao/integrations/telegram"
|
|
"github.com/yaoapp/yao/openapi/response"
|
|
)
|
|
|
|
// VerifyIntegrationRequest — POST body for credential verification.
|
|
type VerifyIntegrationRequest struct {
|
|
Provider string `json:"provider" binding:"required"` // telegram | feishu | dingtalk | discord
|
|
Config map[string]any `json:"config" binding:"required"`
|
|
}
|
|
|
|
// VerifyIntegrationResponse — returned to the frontend.
|
|
type VerifyIntegrationResponse struct {
|
|
Valid bool `json:"valid"`
|
|
Info map[string]any `json:"info,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// VerifyIntegration tests whether the supplied credentials are valid
|
|
// by making a lightweight API call to the target platform.
|
|
//
|
|
// POST /v1/agent/robots/integrations/verify
|
|
func VerifyIntegration(c *gin.Context) {
|
|
var req VerifyIntegrationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.RespondWithError(c, response.StatusBadRequest, &response.ErrorResponse{
|
|
Code: response.ErrInvalidRequest.Code,
|
|
ErrorDescription: "Invalid request body: " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
var resp VerifyIntegrationResponse
|
|
|
|
switch req.Provider {
|
|
case "telegram":
|
|
resp = verifyTelegram(ctx, req.Config)
|
|
case "feishu":
|
|
resp = verifyFeishu(ctx, req.Config)
|
|
case "dingtalk":
|
|
resp = verifyDingtalk(ctx, req.Config)
|
|
case "discord":
|
|
resp = verifyDiscord(ctx, req.Config)
|
|
default:
|
|
response.RespondWithError(c, response.StatusBadRequest, &response.ErrorResponse{
|
|
Code: response.ErrInvalidRequest.Code,
|
|
ErrorDescription: fmt.Sprintf("unsupported provider: %s", req.Provider),
|
|
})
|
|
return
|
|
}
|
|
|
|
response.RespondWithSuccess(c, response.StatusOK, resp)
|
|
}
|
|
|
|
func str(m map[string]any, key string) string {
|
|
v, _ := m[key].(string)
|
|
return v
|
|
}
|
|
|
|
func verifyTelegram(ctx context.Context, cfg map[string]any) VerifyIntegrationResponse {
|
|
token := str(cfg, "bot_token")
|
|
if token == "" {
|
|
return VerifyIntegrationResponse{Valid: false, Error: "bot_token is required"}
|
|
}
|
|
|
|
var opts []telegram.BotOption
|
|
if host := str(cfg, "host"); host != "" {
|
|
opts = append(opts, telegram.WithAPIBase(host))
|
|
}
|
|
|
|
bot := telegram.NewBot(token, "", opts...)
|
|
user, err := bot.GetMe(ctx)
|
|
if err != nil {
|
|
return VerifyIntegrationResponse{Valid: false, Error: err.Error()}
|
|
}
|
|
|
|
info := map[string]any{
|
|
"id": user.ID,
|
|
"username": user.Username,
|
|
"name": user.FirstName,
|
|
}
|
|
if user.LastName != "" {
|
|
info["name"] = user.FirstName + " " + user.LastName
|
|
}
|
|
return VerifyIntegrationResponse{Valid: true, Info: info}
|
|
}
|
|
|
|
func verifyFeishu(ctx context.Context, cfg map[string]any) VerifyIntegrationResponse {
|
|
appID := str(cfg, "app_id")
|
|
appSecret := str(cfg, "app_secret")
|
|
if appID == "" || appSecret == "" {
|
|
return VerifyIntegrationResponse{Valid: false, Error: "app_id and app_secret are required"}
|
|
}
|
|
|
|
bot := feishu.NewBot(appID, appSecret)
|
|
|
|
resp, err := bot.Client().GetTenantAccessTokenBySelfBuiltApp(ctx,
|
|
&larkcore.SelfBuiltTenantAccessTokenReq{AppID: appID, AppSecret: appSecret})
|
|
if err != nil {
|
|
return VerifyIntegrationResponse{Valid: false, Error: err.Error()}
|
|
}
|
|
if resp == nil || !resp.Success() {
|
|
msg := "failed to obtain tenant access token"
|
|
if resp != nil {
|
|
msg = fmt.Sprintf("code=%d msg=%s", resp.Code, resp.Msg)
|
|
}
|
|
return VerifyIntegrationResponse{Valid: false, Error: msg}
|
|
}
|
|
|
|
return VerifyIntegrationResponse{
|
|
Valid: true,
|
|
Info: map[string]any{
|
|
"app_id": appID,
|
|
"status": "credentials_valid",
|
|
},
|
|
}
|
|
}
|
|
|
|
func verifyDingtalk(ctx context.Context, cfg map[string]any) VerifyIntegrationResponse {
|
|
clientID := str(cfg, "client_id")
|
|
clientSecret := str(cfg, "client_secret")
|
|
if clientID == "" || clientSecret == "" {
|
|
return VerifyIntegrationResponse{Valid: false, Error: "client_id and client_secret are required"}
|
|
}
|
|
|
|
bot := dingtalk.NewBot(clientID, clientSecret)
|
|
if err := bot.GetBotInfo(ctx); err != nil {
|
|
return VerifyIntegrationResponse{Valid: false, Error: err.Error()}
|
|
}
|
|
|
|
return VerifyIntegrationResponse{
|
|
Valid: true,
|
|
Info: map[string]any{
|
|
"client_id": clientID,
|
|
"status": "credentials_valid",
|
|
},
|
|
}
|
|
}
|
|
|
|
func verifyDiscord(ctx context.Context, cfg map[string]any) VerifyIntegrationResponse {
|
|
botToken := str(cfg, "bot_token")
|
|
if botToken == "" {
|
|
return VerifyIntegrationResponse{Valid: false, Error: "bot_token is required"}
|
|
}
|
|
|
|
appID := str(cfg, "app_id")
|
|
bot, err := discord.NewBot(botToken, appID)
|
|
if err != nil {
|
|
return VerifyIntegrationResponse{Valid: false, Error: err.Error()}
|
|
}
|
|
|
|
user, err := bot.BotUser()
|
|
if err != nil {
|
|
return VerifyIntegrationResponse{Valid: false, Error: err.Error()}
|
|
}
|
|
|
|
return VerifyIntegrationResponse{
|
|
Valid: true,
|
|
Info: map[string]any{
|
|
"id": user.ID,
|
|
"username": user.Username,
|
|
"name": user.GlobalName,
|
|
},
|
|
}
|
|
}
|