mirror of
https://github.com/YFGaia/dify-plus.git
synced 2026-06-04 10:14:00 +08:00
fix: 综合修复(请求头打印、邮箱/用户名匹配、密钥显示、签名校验、删除报错等)
Made-with: Cursor
This commit is contained in:
@@ -23,6 +23,13 @@ type ForwardProxyApi struct{}
|
|||||||
// @Param path path string true "上游路径"
|
// @Param path path string true "上游路径"
|
||||||
// @Router /gaia/forward/proxy/{path} [get,post,put,patch,delete]
|
// @Router /gaia/forward/proxy/{path} [get,post,put,patch,delete]
|
||||||
func (f *ForwardProxyApi) ForwardProxy(c *gin.Context) {
|
func (f *ForwardProxyApi) ForwardProxy(c *gin.Context) {
|
||||||
|
// 打印请求 Header,便于排查转发问题
|
||||||
|
global.GVA_LOG.Info("ForwardProxy 请求头",
|
||||||
|
zap.Any("headers", c.Request.Header),
|
||||||
|
zap.String("method", c.Request.Method),
|
||||||
|
zap.String("path", c.Request.URL.Path),
|
||||||
|
)
|
||||||
|
|
||||||
// 1. 读取转发配置
|
// 1. 读取转发配置
|
||||||
integrate := systemIntegratedService.GetIntegratedConfig(gaiaModel.SystemIntegrationDingTalk)
|
integrate := systemIntegratedService.GetIntegratedConfig(gaiaModel.SystemIntegrationDingTalk)
|
||||||
configMap, err := systemIntegratedService.ParseDingTalkConfig(integrate.Config)
|
configMap, err := systemIntegratedService.ParseDingTalkConfig(integrate.Config)
|
||||||
@@ -33,12 +40,24 @@ func (f *ForwardProxyApi) ForwardProxy(c *gin.Context) {
|
|||||||
|
|
||||||
// 2. 获取并校验 forwarding token(存在有效 Token 即视为开启转发能力)
|
// 2. 获取并校验 forwarding token(存在有效 Token 即视为开启转发能力)
|
||||||
dingId := c.GetHeader("X-Ding-Id")
|
dingId := c.GetHeader("X-Ding-Id")
|
||||||
|
apiKey := c.GetHeader("X-Api-Key")
|
||||||
bearer := c.GetHeader("Authorization")
|
bearer := c.GetHeader("Authorization")
|
||||||
token := c.GetHeader("X-Forward-Token")
|
token := c.GetHeader("X-Forward-Token")
|
||||||
if len(bearer) > 7 && len(dingId) == 0 {
|
|
||||||
// 从 Token 中验签并提取 ding_id
|
if (len(bearer) > gaiaModel.BearerLength || len(apiKey) > gaiaModel.BearerLength) && len(dingId) == 0 {
|
||||||
dingId, err = systemIntegratedService.ParseForwardToken(bearer[7:], configMap.ForwardConfig.Tokens)
|
if len(bearer) > gaiaModel.BearerLength {
|
||||||
if err != nil {
|
if bearer[:gaiaModel.BearerLength] == "Bearer " {
|
||||||
|
bearer = bearer[gaiaModel.BearerLength:]
|
||||||
|
}
|
||||||
|
} else if len(apiKey) > gaiaModel.BearerLength {
|
||||||
|
if apiKey[:gaiaModel.BearerLength] == "Bearer " {
|
||||||
|
bearer = apiKey[gaiaModel.BearerLength:]
|
||||||
|
} else {
|
||||||
|
bearer = apiKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dingId, err = systemIntegratedService.ParseForwardToken(bearer, configMap.ForwardConfig.Tokens); err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{"message": "Token 验证失败: " + err.Error()}})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{"message": "Token 验证失败: " + err.Error()}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package gaia
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||||
@@ -14,6 +17,7 @@ import (
|
|||||||
"github.com/flipped-aurora/gin-vue-admin/server/model/gaia/request"
|
"github.com/flipped-aurora/gin-vue-admin/server/model/gaia/request"
|
||||||
gaiaResp "github.com/flipped-aurora/gin-vue-admin/server/model/gaia/response"
|
gaiaResp "github.com/flipped-aurora/gin-vue-admin/server/model/gaia/response"
|
||||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||||
|
serviceGaia "github.com/flipped-aurora/gin-vue-admin/server/service/gaia"
|
||||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -154,10 +158,11 @@ func (systemApi *SystemApi) GetForwardTokens(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tokens := make([]gaiaResp.ForwardTokenInfo, 0, len(configMap.ForwardConfig.Tokens))
|
tokens := make([]gaiaResp.ForwardTokenInfo, 0, len(configMap.ForwardConfig.Tokens))
|
||||||
for _, token := range configMap.ForwardConfig.Tokens {
|
for i, token := range configMap.ForwardConfig.Tokens {
|
||||||
tokens = append(tokens, gaiaResp.ForwardTokenInfo{
|
tokens = append(tokens, gaiaResp.ForwardTokenInfo{
|
||||||
ID: utils.AddAsteriskToString(token.ID),
|
ID: utils.AddAsteriskToString(token.TokenSecret),
|
||||||
CreatedAt: token.CreatedAt,
|
CreatedAt: token.CreatedAt,
|
||||||
|
Seq: i + 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,15 +210,24 @@ func (systemApi *SystemApi) CreateForwardToken(c *gin.Context) {
|
|||||||
// 生成唯一 ID 和哈希
|
// 生成唯一 ID 和哈希
|
||||||
tokenID := "tok_" + uuid.New().String()
|
tokenID := "tok_" + uuid.New().String()
|
||||||
tokenHash := fmt.Sprintf("%x", sha256.Sum256([]byte(req.Token)))
|
tokenHash := fmt.Sprintf("%x", sha256.Sum256([]byte(req.Token)))
|
||||||
|
// 生成 HMAC 签名密钥(仅创建时回传一次)
|
||||||
|
secretBytes := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(secretBytes); err != nil {
|
||||||
|
response.FailWithMessage("生成 TokenSecret 失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tokenSecret := base64.RawURLEncoding.EncodeToString(secretBytes)
|
||||||
|
|
||||||
newToken := request.ForwardToken{
|
newToken := request.ForwardToken{
|
||||||
ID: tokenID,
|
ID: tokenID,
|
||||||
TokenHash: tokenHash,
|
TokenHash: tokenHash,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
|
TokenSecret: tokenSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加到配置
|
// 添加到配置
|
||||||
configMap.ForwardConfig.Tokens = append(configMap.ForwardConfig.Tokens, newToken)
|
configMap.ForwardConfig.Tokens = append(configMap.ForwardConfig.Tokens, newToken)
|
||||||
|
seq := len(configMap.ForwardConfig.Tokens) // 1..N
|
||||||
configJSON, _ := json.Marshal(configMap)
|
configJSON, _ := json.Marshal(configMap)
|
||||||
integrate.Config = string(configJSON)
|
integrate.Config = string(configJSON)
|
||||||
|
|
||||||
@@ -225,9 +239,10 @@ func (systemApi *SystemApi) CreateForwardToken(c *gin.Context) {
|
|||||||
|
|
||||||
// 返回明文 token(仅此次展示)
|
// 返回明文 token(仅此次展示)
|
||||||
response.OkWithData(gin.H{
|
response.OkWithData(gin.H{
|
||||||
"id": tokenID,
|
"seq": seq,
|
||||||
"token": req.Token,
|
"token": req.Token,
|
||||||
"created_at": newToken.CreatedAt,
|
"token_secret": tokenSecret,
|
||||||
|
"created_at": newToken.CreatedAt,
|
||||||
}, c)
|
}, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,14 +252,19 @@ func (systemApi *SystemApi) CreateForwardToken(c *gin.Context) {
|
|||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @accept application/json
|
// @accept application/json
|
||||||
// @Produce application/json
|
// @Produce application/json
|
||||||
// @Param id path string true "Token ID"
|
// @Param seq path int true "Token 序列号(从列表获取,1..N)"
|
||||||
// @Param password body string true "当前用户密码"
|
// @Param password body string true "当前用户密码"
|
||||||
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||||
// @Router /gaia/system/forward-tokens/:id [delete]
|
// @Router /gaia/system/forward-tokens/:seq [delete]
|
||||||
func (systemApi *SystemApi) DeleteForwardToken(c *gin.Context) {
|
func (systemApi *SystemApi) DeleteForwardToken(c *gin.Context) {
|
||||||
tokenID := c.Param("id")
|
seqStr := c.Param("seq")
|
||||||
if tokenID == "" {
|
if seqStr == "" {
|
||||||
response.FailWithMessage("Token ID 不能为空", c)
|
response.FailWithMessage("Token 序列号不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seq, err := strconv.Atoi(seqStr)
|
||||||
|
if err != nil || seq <= 0 {
|
||||||
|
response.FailWithMessage("Token 序列号非法", c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,14 +276,22 @@ func (systemApi *SystemApi) DeleteForwardToken(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证当前用户密码
|
// 验证当前用户密码(使用 Dify account 密码体系)
|
||||||
userID := utils.GetUserUuid(c).String()
|
userID := utils.GetUserUuid(c).String()
|
||||||
var user system.SysUser
|
var user system.SysUser
|
||||||
if err := global.GVA_DB.Select("password").First(&user, userID).Error; err != nil {
|
if err := global.GVA_DB.Select("email").Where(
|
||||||
|
"uuid = ?", userID).First(&user).Error; err != nil {
|
||||||
response.FailWithMessage("查询用户失败:"+err.Error(), c)
|
response.FailWithMessage("查询用户失败:"+err.Error(), c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !utils.BcryptCheck(req.Password, user.Password) {
|
account, err := user.GetAccount()
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage("查询账号失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var pwd serviceGaia.PasswdEncode
|
||||||
|
if ok, pwdErr := pwd.ComparePassword(
|
||||||
|
req.Password, account.Password, account.PasswordSalt); pwdErr != nil || !ok {
|
||||||
response.FailWithMessage("密码错误", c)
|
response.FailWithMessage("密码错误", c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -272,34 +300,28 @@ func (systemApi *SystemApi) DeleteForwardToken(c *gin.Context) {
|
|||||||
integrate := systemIntegratedService.GetIntegratedConfig(gaia.SystemIntegrationDingTalk)
|
integrate := systemIntegratedService.GetIntegratedConfig(gaia.SystemIntegrationDingTalk)
|
||||||
var configMap request.DingTalkConfigRequest
|
var configMap request.DingTalkConfigRequest
|
||||||
if integrate.Config != "" {
|
if integrate.Config != "" {
|
||||||
if err := json.Unmarshal([]byte(integrate.Config), &configMap); err != nil {
|
if err = json.Unmarshal([]byte(integrate.Config), &configMap); err != nil {
|
||||||
response.FailWithMessage("解析配置失败:"+err.Error(), c)
|
response.FailWithMessage("解析配置失败:"+err.Error(), c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找并删除 token
|
// 查找并删除 token
|
||||||
found := false
|
if seq > len(configMap.ForwardConfig.Tokens) {
|
||||||
newTokens := make([]request.ForwardToken, 0, len(configMap.ForwardConfig.Tokens))
|
|
||||||
for _, token := range configMap.ForwardConfig.Tokens {
|
|
||||||
if token.ID == tokenID {
|
|
||||||
found = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newTokens = append(newTokens, token)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
response.FailWithMessage("Token 不存在", c)
|
response.FailWithMessage("Token 不存在", c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
idx := seq - 1
|
||||||
|
newTokens := make([]request.ForwardToken, 0, len(configMap.ForwardConfig.Tokens)-1)
|
||||||
|
newTokens = append(newTokens, configMap.ForwardConfig.Tokens[:idx]...)
|
||||||
|
newTokens = append(newTokens, configMap.ForwardConfig.Tokens[idx+1:]...)
|
||||||
|
|
||||||
// 更新配置
|
// 更新配置
|
||||||
configMap.ForwardConfig.Tokens = newTokens
|
configMap.ForwardConfig.Tokens = newTokens
|
||||||
configJSON, _ := json.Marshal(configMap)
|
configJSON, _ := json.Marshal(configMap)
|
||||||
integrate.Config = string(configJSON)
|
integrate.Config = string(configJSON)
|
||||||
|
|
||||||
if err := systemIntegratedService.SetIntegratedConfig(integrate, "", false); err != nil {
|
if err = systemIntegratedService.SetIntegratedConfig(integrate, "", false); err != nil {
|
||||||
response.FailWithMessage("保存失败:"+err.Error(), c)
|
response.FailWithMessage("保存失败:"+err.Error(), c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package response
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// ForwardTokenInfo 转发 Token 列表项(脱敏后的 Token ID)
|
// ForwardTokenInfo 转发 Token 列表项(不暴露内部 ID)
|
||||||
type ForwardTokenInfo struct {
|
type ForwardTokenInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"` // token
|
||||||
|
Seq int `json:"seq"` // 1..N 序列号(用于删除)
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const SystemIntegrationDingTalk = uint(1) // 钉钉集成
|
|||||||
const SystemIntegrationWeiXin = uint(2) // 微信集成
|
const SystemIntegrationWeiXin = uint(2) // 微信集成
|
||||||
const SystemIntegrationFeiShu = uint(3) // 飞书集成
|
const SystemIntegrationFeiShu = uint(3) // 飞书集成
|
||||||
const SystemIntegrationOAuth2 = uint(4) // OAuth2集成
|
const SystemIntegrationOAuth2 = uint(4) // OAuth2集成
|
||||||
|
const BearerLength = 7 // OAuth2集成
|
||||||
|
|
||||||
// SystemIntegration 系统集成表
|
// SystemIntegration 系统集成表
|
||||||
type SystemIntegration struct {
|
type SystemIntegration struct {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func (s *SystemRouter) InitSystemRouter(Router *gin.RouterGroup) {
|
|||||||
// 转发 Token 管理
|
// 转发 Token 管理
|
||||||
systemRouter.GET("forward-tokens", systemApi.GetForwardTokens) // 获取转发 Token 列表
|
systemRouter.GET("forward-tokens", systemApi.GetForwardTokens) // 获取转发 Token 列表
|
||||||
systemRouter.POST("forward-tokens", systemApi.CreateForwardToken) // 新增转发 Token
|
systemRouter.POST("forward-tokens", systemApi.CreateForwardToken) // 新增转发 Token
|
||||||
systemRouter.DELETE("forward-tokens/:id", systemApi.DeleteForwardToken) // 删除转发 Token
|
systemRouter.DELETE("forward-tokens/:seq", systemApi.DeleteForwardToken) // 删除转发 Token(按序列号)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -241,6 +240,7 @@ func (e *SystemIntegratedService) DingTalkCodeLogin(req request.GaiaDingTalkLogi
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dingUser map[string]interface{}
|
var dingUser map[string]interface{}
|
||||||
|
fmt.Println("sssssssss", string(userBody))
|
||||||
if err = json.Unmarshal(userBody, &dingUser); err != nil {
|
if err = json.Unmarshal(userBody, &dingUser); err != nil {
|
||||||
return nil, fmt.Errorf("解析钉钉用户信息失败")
|
return nil, fmt.Errorf("解析钉钉用户信息失败")
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,8 @@ func (e *SystemIntegratedService) DingTalkCodeLogin(req request.GaiaDingTalkLogi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析邮箱配置
|
// 解析用户名配置
|
||||||
|
var emailList []string
|
||||||
var configMap request.DingTalkConfigRequest
|
var configMap request.DingTalkConfigRequest
|
||||||
var emailConfig request.EmailApiConfig
|
var emailConfig request.EmailApiConfig
|
||||||
if integrate.Config != "" {
|
if integrate.Config != "" {
|
||||||
@@ -271,13 +272,11 @@ func (e *SystemIntegratedService) DingTalkCodeLogin(req request.GaiaDingTalkLogi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 优先通过邮箱 API 获取邮箱(新格式)
|
// 优先通过用户名 API 获取用户名(新格式)
|
||||||
if emailConfig.Enabled && dingId != "" {
|
if emailConfig.Enabled && dingId != "" {
|
||||||
email, apiErr := e.callEmailApi(dingId, emailConfig)
|
emailList, err = e.callEmailApi(dingId, emailConfig)
|
||||||
if apiErr == nil && email != "" {
|
if err == nil && len(emailList) > 0 {
|
||||||
global.GVA_LOG.Info("DingTalkCodeLogin: 通过第三方邮箱 API 获取邮箱",
|
sysUser, findErr := e.findUserByEmail(emailList)
|
||||||
zap.String("ding_id", dingId), zap.String("email", email))
|
|
||||||
sysUser, findErr := e.findUserByEmail(email)
|
|
||||||
if findErr != nil {
|
if findErr != nil {
|
||||||
return nil, findErr
|
return nil, findErr
|
||||||
}
|
}
|
||||||
@@ -288,7 +287,7 @@ func (e *SystemIntegratedService) DingTalkCodeLogin(req request.GaiaDingTalkLogi
|
|||||||
return &response.GaiaLoginResult{User: *sysUser, Token: token, RedirectURI: req.RedirectURI, State: req.State}, nil
|
return &response.GaiaLoginResult{User: *sysUser, Token: token, RedirectURI: req.RedirectURI, State: req.State}, nil
|
||||||
}
|
}
|
||||||
global.GVA_LOG.Warn("DingTalkCodeLogin: 第三方邮箱 API 获取失败,尝试钉钉直接返回邮箱",
|
global.GVA_LOG.Warn("DingTalkCodeLogin: 第三方邮箱 API 获取失败,尝试钉钉直接返回邮箱",
|
||||||
zap.String("ding_id", dingId), zap.Error(apiErr))
|
zap.String("ding_id", dingId), zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回退:直接从钉钉用户信息获取邮箱
|
// 回退:直接从钉钉用户信息获取邮箱
|
||||||
@@ -301,7 +300,7 @@ func (e *SystemIntegratedService) DingTalkCodeLogin(req request.GaiaDingTalkLogi
|
|||||||
return nil, fmt.Errorf("钉钉未返回邮箱")
|
return nil, fmt.Errorf("钉钉未返回邮箱")
|
||||||
}
|
}
|
||||||
|
|
||||||
sysUser, err := e.findUserByEmail(email)
|
sysUser, err := e.findUserByEmail([]string{email})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -318,7 +317,8 @@ func getStringFromMap(m map[string]interface{}, keys ...string) string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if v, ok := m[k]; ok && v != nil {
|
if v, ok := m[k]; ok && v != nil {
|
||||||
if s, ok := v.(string); ok {
|
var s string
|
||||||
|
if s, ok = v.(string); ok {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -384,21 +384,14 @@ func getStringByPathOrKeys(m map[string]interface{}, path string, fallbackKeys .
|
|||||||
return getStringFromMap(m, fallbackKeys...)
|
return getStringFromMap(m, fallbackKeys...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// findUserByEmail 按邮箱查找已存在的用户(需在 gaia.accounts 中有对应记录方可签发 JWT)
|
// findUserByEmail 按username查找已存在的用户(需在 gaia.accounts 中有对应记录方可签发 JWT)
|
||||||
func (e *SystemIntegratedService) findUserByEmail(email string) (*system.SysUser, error) {
|
func (e *SystemIntegratedService) findUserByEmail(mailList []string) (*system.SysUser, error) {
|
||||||
var u system.SysUser
|
|
||||||
var mailList []string
|
|
||||||
mailList = append(mailList, email)
|
|
||||||
parts := strings.Split(email, "@")
|
|
||||||
defaultMail := os.Getenv(gaia.EmailDomainEnv)
|
|
||||||
if len(defaultMail) > 0 && len(parts) == 2 {
|
|
||||||
mailList = append(mailList, parts[0]+"@"+defaultMail)
|
|
||||||
}
|
|
||||||
// 查询关联邮箱
|
// 查询关联邮箱
|
||||||
|
var u system.SysUser
|
||||||
if err := global.GVA_DB.Where("email IN (?)", mailList).Preload(
|
if err := global.GVA_DB.Where("email IN (?)", mailList).Preload(
|
||||||
"Authorities").Preload("Authority").First(&u).Error; err != nil {
|
"Authorities").Preload("Authority").First(&u).Error; err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fmt.Errorf("邮箱%s尚未开通账号,请联系管理员", email)
|
return nil, fmt.Errorf("%s尚未开通账号,请联系管理员", mailList[0])
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -410,20 +403,19 @@ func (e *SystemIntegratedService) findUserByEmail(email string) (*system.SysUser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// findUserByEmailOrPhone 按邮箱或用户唯一标识(如手机号)查找用户,优先邮箱
|
// findUserByEmailOrPhone 按邮箱或用户唯一标识(如手机号)查找用户,优先邮箱
|
||||||
func (e *SystemIntegratedService) findUserByEmailOrPhone(email, userID string) (*system.SysUser, error) {
|
func (e *SystemIntegratedService) findUserByEmailOrPhone(mail, userID string) (u *system.SysUser, err error) {
|
||||||
if email != "" {
|
if mail != "" {
|
||||||
u, err := e.findUserByEmail(email)
|
if u, err = e.findUserByEmail([]string{mail}); err == nil {
|
||||||
if err == nil {
|
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
// 仅当“未开通”时再尝试按 userID(phone) 查,其他错误直接返回
|
// 仅当“未开通”时再尝试按 userID(phone) 查,其他错误直接返回
|
||||||
if err != nil && !strings.Contains(err.Error(), "尚未开通") {
|
if !strings.Contains(err.Error(), "尚未开通") {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if userID != "" {
|
if userID != "" {
|
||||||
var u system.SysUser
|
if err = global.GVA_DB.Where("phone = ?", userID).Preload(
|
||||||
if err := global.GVA_DB.Where("phone = ?", userID).Preload("Authorities").Preload("Authority").First(&u).Error; err != nil {
|
"Authorities").Preload("Authority").First(&u).Error; err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fmt.Errorf("该用户唯一标识尚未开通后台账号,请联系管理员")
|
return nil, fmt.Errorf("该用户唯一标识尚未开通后台账号,请联系管理员")
|
||||||
}
|
}
|
||||||
@@ -432,7 +424,7 @@ func (e *SystemIntegratedService) findUserByEmailOrPhone(email, userID string) (
|
|||||||
if u.Enable != 1 {
|
if u.Enable != 1 {
|
||||||
return nil, fmt.Errorf("账号已被禁用")
|
return nil, fmt.Errorf("账号已被禁用")
|
||||||
}
|
}
|
||||||
return &u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("无法从 OAuth2 用户信息中获取邮箱或用户唯一标识")
|
return nil, fmt.Errorf("无法从 OAuth2 用户信息中获取邮箱或用户唯一标识")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -727,23 +726,32 @@ func buildURL(baseURL string, config request.EmailApiConfig, dingId string) stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// callEmailApi 调用第三方邮箱 API,使用 ding_id(用户名) 获取邮箱
|
// callEmailApi 调用第三方邮箱 API,使用 ding_id(用户名) 获取邮箱
|
||||||
func (e *SystemIntegratedService) callEmailApi(dingId string, config request.EmailApiConfig) (string, error) {
|
func (e *SystemIntegratedService) callEmailApi(
|
||||||
|
dingId string, config request.EmailApiConfig) (mailList []string, err error) {
|
||||||
|
// init
|
||||||
respBody, _, err := e.doEmailApiRequest(dingId, config)
|
respBody, _, err := e.doEmailApiRequest(dingId, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return mailList, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var respJSON map[string]interface{}
|
var respJSON map[string]interface{}
|
||||||
if err = json.Unmarshal(respBody, &respJSON); err != nil {
|
if err = json.Unmarshal(respBody, &respJSON); err != nil {
|
||||||
return "", fmt.Errorf("解析响应 JSON 失败:%s", err.Error())
|
return mailList, fmt.Errorf("解析响应 JSON 失败:%s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
email := extractJSONPathAdvanced(respJSON, config.ResponseEmailField)
|
email := extractJSONPathAdvanced(respJSON, config.ResponseEmailField)
|
||||||
if email == "" {
|
if email == "" {
|
||||||
return "", fmt.Errorf("响应中未找到邮箱(路径:%s)", config.ResponseEmailField)
|
return mailList, fmt.Errorf("响应中未找到邮箱(路径:%s)", config.ResponseEmailField)
|
||||||
|
}
|
||||||
|
//
|
||||||
|
mailList = append(mailList, email)
|
||||||
|
parts := strings.Split(email, "@")
|
||||||
|
defaultMail := os.Getenv(gaia.EmailDomainEnv)
|
||||||
|
if len(defaultMail) > 0 && len(parts) > 1 && len(parts[0]) > 0 {
|
||||||
|
mailList = append(mailList, parts[0]+"@"+defaultMail)
|
||||||
}
|
}
|
||||||
|
|
||||||
return email, nil
|
return mailList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// doEmailApiRequest 构建并执行邮箱 API 请求,返回响应体字节和状态码
|
// doEmailApiRequest 构建并执行邮箱 API 请求,返回响应体字节和状态码
|
||||||
@@ -920,10 +928,8 @@ func (e *SystemIntegratedService) ParseForwardToken(
|
|||||||
if t.TokenSecret == "" {
|
if t.TokenSecret == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
secret, err := hex.DecodeString(t.TokenSecret)
|
// 直接使用原始字节作为 HMAC 密钥,兼容任意字符串格式的密钥
|
||||||
if err != nil {
|
secret := []byte(t.TokenSecret)
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 验证 HMAC 签名
|
// 验证 HMAC 签名
|
||||||
mac := hmac.New(sha256.New, secret)
|
mac := hmac.New(sha256.New, secret)
|
||||||
mac.Write([]byte(payloadB64))
|
mac.Write([]byte(payloadB64))
|
||||||
@@ -952,13 +958,13 @@ func (e *SystemIntegratedService) ParseForwardToken(
|
|||||||
|
|
||||||
// ResolveAccountByDingId 通过钉钉 ID 解析 gaia account_id
|
// ResolveAccountByDingId 通过钉钉 ID 解析 gaia account_id
|
||||||
// 解析顺序:Redis 缓存 → AccountDingTalkExtend 本地表 → 第三方 EmailApi(邮箱 API)
|
// 解析顺序:Redis 缓存 → AccountDingTalkExtend 本地表 → 第三方 EmailApi(邮箱 API)
|
||||||
func (e *SystemIntegratedService) ResolveAccountByDingId(dingId string, apiConfig request.EmailApiConfig) (string, error) {
|
func (e *SystemIntegratedService) ResolveAccountByDingId(
|
||||||
ctx := context.Background()
|
dingId string, apiConfig request.EmailApiConfig) (string, error) {
|
||||||
redisKey := "gaia:forward:ding:" + dingId
|
|
||||||
|
|
||||||
// 1. 查 Redis 缓存
|
// 1. 查 Redis 缓存
|
||||||
|
ctx := context.Background()
|
||||||
|
redisKey := "gaia:forward:ding:" + dingId
|
||||||
if cached, err := global.GVA_REDIS.Get(ctx, redisKey).Result(); err == nil && cached != "" {
|
if cached, err := global.GVA_REDIS.Get(ctx, redisKey).Result(); err == nil && cached != "" {
|
||||||
global.GVA_LOG.Info("ResolveAccountByDingId: Redis 命中", zap.String("ding_id", dingId), zap.String("account_id", cached))
|
|
||||||
return cached, nil
|
return cached, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -966,7 +972,6 @@ func (e *SystemIntegratedService) ResolveAccountByDingId(dingId string, apiConfi
|
|||||||
var extend gaia.AccountDingTalkExtend
|
var extend gaia.AccountDingTalkExtend
|
||||||
if err := global.GVA_DB.Where("ding_talk = ?", dingId).First(&extend).Error; err == nil {
|
if err := global.GVA_DB.Where("ding_talk = ?", dingId).First(&extend).Error; err == nil {
|
||||||
accountID := extend.ID.String()
|
accountID := extend.ID.String()
|
||||||
global.GVA_LOG.Info("ResolveAccountByDingId: 本地表命中", zap.String("ding_id", dingId), zap.String("account_id", accountID))
|
|
||||||
global.GVA_REDIS.Set(ctx, redisKey, accountID, 24*time.Hour)
|
global.GVA_REDIS.Set(ctx, redisKey, accountID, 24*time.Hour)
|
||||||
return accountID, nil
|
return accountID, nil
|
||||||
}
|
}
|
||||||
@@ -984,7 +989,7 @@ func (e *SystemIntegratedService) ResolveAccountByDingId(dingId string, apiConfi
|
|||||||
// 4. 按邮箱查 accounts 表(匹配 email 字段)
|
// 4. 按邮箱查 accounts 表(匹配 email 字段)
|
||||||
var account gaia.Account
|
var account gaia.Account
|
||||||
if err = global.GVA_DB.Where("email = ?", email).First(&account).Error; err != nil {
|
if err = global.GVA_DB.Where("email = ?", email).First(&account).Error; err != nil {
|
||||||
return "", fmt.Errorf("邮箱 %s 不存在(来自第三方邮箱 API)", email)
|
return "", fmt.Errorf("用户 %s 不存在(来自第三方邮箱 API)", email)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountID := account.ID.String()
|
accountID := account.ID.String()
|
||||||
@@ -997,11 +1002,5 @@ func (e *SystemIntegratedService) ResolveAccountByDingId(dingId string, apiConfi
|
|||||||
|
|
||||||
// 6. 写 Redis 缓存
|
// 6. 写 Redis 缓存
|
||||||
global.GVA_REDIS.Set(ctx, redisKey, accountID, 24*time.Hour)
|
global.GVA_REDIS.Set(ctx, redisKey, accountID, 24*time.Hour)
|
||||||
|
|
||||||
global.GVA_LOG.Info("ResolveAccountByDingId: 第三方邮箱 API 解析成功",
|
|
||||||
zap.String("ding_id", dingId),
|
|
||||||
zap.String("email", email),
|
|
||||||
zap.String("account_id", accountID))
|
|
||||||
|
|
||||||
return accountID, nil
|
return accountID, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,10 +80,10 @@ export const createForwardToken = (data) => {
|
|||||||
// @Tags systrm
|
// @Tags systrm
|
||||||
// @Summary 删除转发 Token
|
// @Summary 删除转发 Token
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Router /gaia/system/forward-tokens/:id [delete]
|
// @Router /gaia/system/forward-tokens/:seq [delete]
|
||||||
export const deleteForwardToken = (id, password) => {
|
export const deleteForwardToken = (seq, password) => {
|
||||||
return service({
|
return service({
|
||||||
url: `/gaia/system/forward-tokens/${id}`,
|
url: `/gaia/system/forward-tokens/${seq}`,
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
data: { password },
|
data: { password },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -96,17 +96,17 @@
|
|||||||
|
|
||||||
<div class="card-section">
|
<div class="card-section">
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
第三方邮箱配置
|
第三方用户名提取配置
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 dark:bg-slate-800 p-5 border dark:border-slate-700 rounded-lg">
|
<div class="bg-gray-50 dark:bg-slate-800 p-5 border dark:border-slate-700 rounded-lg">
|
||||||
<!-- 基础配置 -->
|
<!-- 基础配置 -->
|
||||||
<div class="flex items-center mb-4">
|
<div class="flex items-center mb-4">
|
||||||
<span class="info-label">邮箱详情的URL:</span>
|
<span class="info-label">第三方的URL:</span>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="openEdit"
|
v-if="openEdit"
|
||||||
v-model="emailApiConfig.url"
|
v-model="emailApiConfig.url"
|
||||||
class="info-value flex-1"
|
class="info-value flex-1"
|
||||||
placeholder="请输入钉钉通过用户名获取邮箱地址的链接地址"
|
placeholder="请输入钉钉id获取用户名的链接地址"
|
||||||
/>
|
/>
|
||||||
<span v-else class="info-value">{{ emailApiConfig.url || '未配置' }}</span>
|
<span v-else class="info-value">{{ emailApiConfig.url || '未配置' }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -403,9 +403,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 邮箱信息提取路径 -->
|
<!-- 用户名路径 -->
|
||||||
<div class="flex items-center mb-4 mt-4">
|
<div class="flex items-center mb-4 mt-4">
|
||||||
<span class="info-label">邮箱信息提取:</span>
|
<span class="info-label">用户名路径:</span>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="openEdit"
|
v-if="openEdit"
|
||||||
v-model="emailApiConfig.response_email_field"
|
v-model="emailApiConfig.response_email_field"
|
||||||
@@ -453,7 +453,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="forwardTokenList" border size="small" class="w-full">
|
<el-table :data="forwardTokenList" border size="small" class="w-full">
|
||||||
<el-table-column label="Token ID" prop="id" min-width="240">
|
<el-table-column label="token" prop="seq" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span class="font-mono text-xs">{{ row.id }}</span>
|
<span class="font-mono text-xs">{{ row.id }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -465,7 +465,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="80" align="center">
|
<el-table-column label="操作" width="80" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="danger" link size="small" @click="openDeleteTokenDialog(row.id)">
|
<el-button type="danger" link size="small" @click="openDeleteTokenDialog(row.seq)">
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -499,29 +499,47 @@
|
|||||||
|
|
||||||
<!-- 新增 Token 弹窗:前端随机生成 → 保存到后端 → 自动复制到剪贴板并提示 -->
|
<!-- 新增 Token 弹窗:前端随机生成 → 保存到后端 → 自动复制到剪贴板并提示 -->
|
||||||
<el-dialog v-model="showCreateTokenDialog" title="新增转发 Token" width="480px" :close-on-click-modal="false">
|
<el-dialog v-model="showCreateTokenDialog" title="新增转发 Token" width="480px" :close-on-click-modal="false">
|
||||||
<div v-if="!newTokenValue">
|
<div v-if="!newPlainToken">
|
||||||
<p class="text-gray-600 mb-4">
|
<p class="text-gray-600 mb-4">
|
||||||
点击「生成并保存」将随机生成 Token,保存后会自动复制到系统剪贴板,请粘贴到安全位置保管。Token 仅展示一次。
|
点击「生成并保存」将随机生成 Token,并返回两种凭证:
|
||||||
|
<br />2)Token Secret(用于生成 Authorization: Bearer ... 的签名密钥)
|
||||||
|
<br />两者仅展示一次,请务必复制到安全位置保管。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<el-alert type="success" title="Token 已生成并已复制到剪贴板,请妥善保管。此处仅展示一次。" :closable="false" class="mb-4" />
|
<el-alert
|
||||||
<el-input v-model="newTokenValue" readonly>
|
type="success"
|
||||||
<template #append>
|
title="Token 已生成,并已将 Token Secret 复制到剪贴板,请妥善保管(以下两项仅展示一次)。"
|
||||||
<el-button @click="copyToken(newTokenValue)">
|
:closable="false"
|
||||||
复制
|
class="mb-4"
|
||||||
</el-button>
|
/>
|
||||||
</template>
|
<div class="mb-3">
|
||||||
</el-input>
|
<div class="text-xs text-gray-500 mb-1">明文 Token(可用于 X-Forward-Token)</div>
|
||||||
|
<el-input v-model="newPlainToken" readonly>
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="copyToken(newPlainToken)">
|
||||||
|
复制 Token
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button v-if="!newTokenValue" @click="showCreateTokenDialog = false">
|
<el-button v-if="!newPlainToken" @click="showCreateTokenDialog = false">
|
||||||
取消
|
取消
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="!newTokenValue" type="primary" :loading="creatingToken" @click="handleCreateToken">
|
<el-button v-if="!newPlainToken" type="primary" :loading="creatingToken" @click="handleCreateToken">
|
||||||
生成并保存
|
生成并保存
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="newTokenValue" type="primary" @click="showCreateTokenDialog = false; newTokenValue = ''; initForm()">
|
<el-button
|
||||||
|
v-else
|
||||||
|
type="primary"
|
||||||
|
@click="
|
||||||
|
showCreateTokenDialog = false;
|
||||||
|
newPlainToken = '';
|
||||||
|
initForm();
|
||||||
|
"
|
||||||
|
>
|
||||||
完成
|
完成
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -534,7 +552,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<el-input v-model="deleteTokenPassword" type="password" placeholder="请输入您的登录密码" show-password />
|
<el-input v-model="deleteTokenPassword" type="password" placeholder="请输入您的登录密码" show-password />
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="showDeleteTokenDialog = false; deleteTokenPassword = ''; deletingTokenId = ''">
|
<el-button @click="showDeleteTokenDialog = false; deleteTokenPassword = ''; deletingTokenSeq = null">
|
||||||
取消
|
取消
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" :loading="deletingToken" @click="handleDeleteToken">
|
<el-button type="danger" :loading="deletingToken" @click="handleDeleteToken">
|
||||||
@@ -624,7 +642,7 @@
|
|||||||
class="ml-auto"
|
class="ml-auto"
|
||||||
@click="selectJsonField(path, item)"
|
@click="selectJsonField(path, item)"
|
||||||
>
|
>
|
||||||
选为邮箱字段
|
选为用户名字段
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -658,10 +676,9 @@ import { ref, computed, watch, nextTick } from 'vue'
|
|||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { QuestionFilled, Loading } from '@element-plus/icons-vue'
|
import { QuestionFilled, Loading } from '@element-plus/icons-vue'
|
||||||
import { useClipboard } from '@vueuse/core'
|
import { useClipboard } from '@vueuse/core'
|
||||||
|
const { copy, isSupported } = useClipboard()
|
||||||
import { getSystemDingTalk, setSystemDingTalk, getForwardTokens, createForwardToken, deleteForwardToken, testEmailApiConfig, getDingTalkTestAuthUrl } from "@/api/gaia/system";
|
import { getSystemDingTalk, setSystemDingTalk, getForwardTokens, createForwardToken, deleteForwardToken, testEmailApiConfig, getDingTalkTestAuthUrl } from "@/api/gaia/system";
|
||||||
|
|
||||||
const { copy: copyToClipboard, isSupported: isClipboardSupported } = useClipboard()
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'IntegratedDingTalk',
|
name: 'IntegratedDingTalk',
|
||||||
})
|
})
|
||||||
@@ -710,13 +727,15 @@ const forwardTokenList = ref([])
|
|||||||
|
|
||||||
// 新增 Token 弹窗(前端随机生成 → 保存 → 复制到剪贴板)
|
// 新增 Token 弹窗(前端随机生成 → 保存 → 复制到剪贴板)
|
||||||
const showCreateTokenDialog = ref(false)
|
const showCreateTokenDialog = ref(false)
|
||||||
const newTokenValue = ref('')
|
// 明文 token:可用于 X-Forward-Token 模式
|
||||||
|
const newPlainToken = ref('')
|
||||||
|
// token_secret:用于生成 Authorization: Bearer ... 的 HMAC 密钥
|
||||||
const creatingToken = ref(false)
|
const creatingToken = ref(false)
|
||||||
|
|
||||||
// 删除 Token 弹窗
|
// 删除 Token 弹窗
|
||||||
const showDeleteTokenDialog = ref(false)
|
const showDeleteTokenDialog = ref(false)
|
||||||
const deleteTokenPassword = ref('')
|
const deleteTokenPassword = ref('')
|
||||||
const deletingTokenId = ref('')
|
const deletingTokenSeq = ref(null)
|
||||||
const deletingToken = ref(false)
|
const deletingToken = ref(false)
|
||||||
|
|
||||||
// 格式化日期
|
// 格式化日期
|
||||||
@@ -747,12 +766,13 @@ const generateToken = () => {
|
|||||||
const copyToken = async (token) => {
|
const copyToken = async (token) => {
|
||||||
if (!token) return
|
if (!token) return
|
||||||
try {
|
try {
|
||||||
if (isClipboardSupported.value) {
|
if (copy) {
|
||||||
await copyToClipboard(token)
|
await copy(token)
|
||||||
} else {
|
} else {
|
||||||
await navigator.clipboard.writeText(token)
|
await navigator.clipboard.writeText(token)
|
||||||
}
|
}
|
||||||
ElMessage({ type: 'success', message: 'Token 已复制到剪贴板' })
|
ElMessage({ type: 'success', message: 'Token 已复制到剪贴板' })
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ElMessage({ type: 'warning', message: '复制失败,请手动复制' })
|
ElMessage({ type: 'warning', message: '复制失败,请手动复制' })
|
||||||
}
|
}
|
||||||
@@ -765,8 +785,9 @@ const handleCreateToken = async () => {
|
|||||||
try {
|
try {
|
||||||
const res = await createForwardToken({ token })
|
const res = await createForwardToken({ token })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
newTokenValue.value = res.data?.token ?? token
|
newPlainToken.value = res.data?.token_secret || ''
|
||||||
await copyToken(newTokenValue.value)
|
// 默认复制 token_secret,方便用于 Bearer Token 生成
|
||||||
|
await copyToken(newPlainToken.value)
|
||||||
} else {
|
} else {
|
||||||
ElMessage({ type: 'error', message: res.msg || '保存失败' })
|
ElMessage({ type: 'error', message: res.msg || '保存失败' })
|
||||||
}
|
}
|
||||||
@@ -776,8 +797,8 @@ const handleCreateToken = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 打开删除 Token 弹窗
|
// 打开删除 Token 弹窗
|
||||||
const openDeleteTokenDialog = (id) => {
|
const openDeleteTokenDialog = (seq) => {
|
||||||
deletingTokenId.value = id
|
deletingTokenSeq.value = seq
|
||||||
deleteTokenPassword.value = ''
|
deleteTokenPassword.value = ''
|
||||||
showDeleteTokenDialog.value = true
|
showDeleteTokenDialog.value = true
|
||||||
}
|
}
|
||||||
@@ -789,13 +810,13 @@ const handleDeleteToken = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
deletingToken.value = true
|
deletingToken.value = true
|
||||||
const res = await deleteForwardToken(deletingTokenId.value, deleteTokenPassword.value)
|
const res = await deleteForwardToken(deletingTokenSeq.value, deleteTokenPassword.value)
|
||||||
deletingToken.value = false
|
deletingToken.value = false
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
ElMessage({ type: 'success', message: '删除成功' })
|
ElMessage({ type: 'success', message: '删除成功' })
|
||||||
showDeleteTokenDialog.value = false
|
showDeleteTokenDialog.value = false
|
||||||
deleteTokenPassword.value = ''
|
deleteTokenPassword.value = ''
|
||||||
deletingTokenId.value = ''
|
deletingTokenSeq.value = null
|
||||||
await initForm()
|
await initForm()
|
||||||
} else {
|
} else {
|
||||||
ElMessage({ type: 'error', message: res.msg || '删除失败' })
|
ElMessage({ type: 'error', message: res.msg || '删除失败' })
|
||||||
|
|||||||
Reference in New Issue
Block a user