option.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package controller
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "one-api/common"
  8. "one-api/model"
  9. "one-api/setting"
  10. "one-api/setting/system_setting"
  11. "regexp"
  12. "strings"
  13. "github.com/gin-gonic/gin"
  14. )
  15. func validateApiInfo(apiInfoStr string) error {
  16. if apiInfoStr == "" {
  17. return nil // 空字符串是合法的
  18. }
  19. var apiInfoList []map[string]interface{}
  20. if err := json.Unmarshal([]byte(apiInfoStr), &apiInfoList); err != nil {
  21. return fmt.Errorf("API信息格式错误:%s", err.Error())
  22. }
  23. // 验证数组长度
  24. if len(apiInfoList) > 50 {
  25. return fmt.Errorf("API信息数量不能超过50个")
  26. }
  27. // 允许的颜色值
  28. validColors := map[string]bool{
  29. "blue": true, "green": true, "cyan": true, "purple": true, "pink": true,
  30. "red": true, "orange": true, "amber": true, "yellow": true, "lime": true,
  31. "light-green": true, "teal": true, "light-blue": true, "indigo": true,
  32. "violet": true, "grey": true,
  33. }
  34. // URL正则表达式
  35. urlRegex := regexp.MustCompile(`^https?://[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*(/.*)?$`)
  36. for i, apiInfo := range apiInfoList {
  37. // 检查必填字段
  38. urlStr, ok := apiInfo["url"].(string)
  39. if !ok || urlStr == "" {
  40. return fmt.Errorf("第%d个API信息缺少URL字段", i+1)
  41. }
  42. route, ok := apiInfo["route"].(string)
  43. if !ok || route == "" {
  44. return fmt.Errorf("第%d个API信息缺少线路描述字段", i+1)
  45. }
  46. description, ok := apiInfo["description"].(string)
  47. if !ok || description == "" {
  48. return fmt.Errorf("第%d个API信息缺少说明字段", i+1)
  49. }
  50. color, ok := apiInfo["color"].(string)
  51. if !ok || color == "" {
  52. return fmt.Errorf("第%d个API信息缺少颜色字段", i+1)
  53. }
  54. // 验证URL格式
  55. if !urlRegex.MatchString(urlStr) {
  56. return fmt.Errorf("第%d个API信息的URL格式不正确", i+1)
  57. }
  58. // 验证URL可解析性
  59. if _, err := url.Parse(urlStr); err != nil {
  60. return fmt.Errorf("第%d个API信息的URL无法解析:%s", i+1, err.Error())
  61. }
  62. // 验证字段长度
  63. if len(urlStr) > 500 {
  64. return fmt.Errorf("第%d个API信息的URL长度不能超过500字符", i+1)
  65. }
  66. if len(route) > 100 {
  67. return fmt.Errorf("第%d个API信息的线路描述长度不能超过100字符", i+1)
  68. }
  69. if len(description) > 200 {
  70. return fmt.Errorf("第%d个API信息的说明长度不能超过200字符", i+1)
  71. }
  72. // 验证颜色值
  73. if !validColors[color] {
  74. return fmt.Errorf("第%d个API信息的颜色值不合法", i+1)
  75. }
  76. // 检查并过滤危险字符(防止XSS)
  77. dangerousChars := []string{"<script", "<iframe", "javascript:", "onload=", "onerror=", "onclick="}
  78. for _, dangerous := range dangerousChars {
  79. if strings.Contains(strings.ToLower(description), dangerous) {
  80. return fmt.Errorf("第%d个API信息的说明包含不允许的内容", i+1)
  81. }
  82. if strings.Contains(strings.ToLower(route), dangerous) {
  83. return fmt.Errorf("第%d个API信息的线路描述包含不允许的内容", i+1)
  84. }
  85. }
  86. }
  87. return nil
  88. }
  89. func GetOptions(c *gin.Context) {
  90. var options []*model.Option
  91. common.OptionMapRWMutex.Lock()
  92. for k, v := range common.OptionMap {
  93. if strings.HasSuffix(k, "Token") || strings.HasSuffix(k, "Secret") || strings.HasSuffix(k, "Key") {
  94. continue
  95. }
  96. options = append(options, &model.Option{
  97. Key: k,
  98. Value: common.Interface2String(v),
  99. })
  100. }
  101. common.OptionMapRWMutex.Unlock()
  102. c.JSON(http.StatusOK, gin.H{
  103. "success": true,
  104. "message": "",
  105. "data": options,
  106. })
  107. return
  108. }
  109. func UpdateOption(c *gin.Context) {
  110. var option model.Option
  111. err := json.NewDecoder(c.Request.Body).Decode(&option)
  112. if err != nil {
  113. c.JSON(http.StatusBadRequest, gin.H{
  114. "success": false,
  115. "message": "无效的参数",
  116. })
  117. return
  118. }
  119. switch option.Key {
  120. case "GitHubOAuthEnabled":
  121. if option.Value == "true" && common.GitHubClientId == "" {
  122. c.JSON(http.StatusOK, gin.H{
  123. "success": false,
  124. "message": "无法启用 GitHub OAuth,请先填入 GitHub Client Id 以及 GitHub Client Secret!",
  125. })
  126. return
  127. }
  128. case "oidc.enabled":
  129. if option.Value == "true" && system_setting.GetOIDCSettings().ClientId == "" {
  130. c.JSON(http.StatusOK, gin.H{
  131. "success": false,
  132. "message": "无法启用 OIDC 登录,请先填入 OIDC Client Id 以及 OIDC Client Secret!",
  133. })
  134. return
  135. }
  136. case "LinuxDOOAuthEnabled":
  137. if option.Value == "true" && common.LinuxDOClientId == "" {
  138. c.JSON(http.StatusOK, gin.H{
  139. "success": false,
  140. "message": "无法启用 LinuxDO OAuth,请先填入 LinuxDO Client Id 以及 LinuxDO Client Secret!",
  141. })
  142. return
  143. }
  144. case "EmailDomainRestrictionEnabled":
  145. if option.Value == "true" && len(common.EmailDomainWhitelist) == 0 {
  146. c.JSON(http.StatusOK, gin.H{
  147. "success": false,
  148. "message": "无法启用邮箱域名限制,请先填入限制的邮箱域名!",
  149. })
  150. return
  151. }
  152. case "WeChatAuthEnabled":
  153. if option.Value == "true" && common.WeChatServerAddress == "" {
  154. c.JSON(http.StatusOK, gin.H{
  155. "success": false,
  156. "message": "无法启用微信登录,请先填入微信登录相关配置信息!",
  157. })
  158. return
  159. }
  160. case "TurnstileCheckEnabled":
  161. if option.Value == "true" && common.TurnstileSiteKey == "" {
  162. c.JSON(http.StatusOK, gin.H{
  163. "success": false,
  164. "message": "无法启用 Turnstile 校验,请先填入 Turnstile 校验相关配置信息!",
  165. })
  166. return
  167. }
  168. case "TelegramOAuthEnabled":
  169. if option.Value == "true" && common.TelegramBotToken == "" {
  170. c.JSON(http.StatusOK, gin.H{
  171. "success": false,
  172. "message": "无法启用 Telegram OAuth,请先填入 Telegram Bot Token!",
  173. })
  174. return
  175. }
  176. case "GroupRatio":
  177. err = setting.CheckGroupRatio(option.Value)
  178. if err != nil {
  179. c.JSON(http.StatusOK, gin.H{
  180. "success": false,
  181. "message": err.Error(),
  182. })
  183. return
  184. }
  185. case "ModelRequestRateLimitGroup":
  186. err = setting.CheckModelRequestRateLimitGroup(option.Value)
  187. if err != nil {
  188. c.JSON(http.StatusOK, gin.H{
  189. "success": false,
  190. "message": err.Error(),
  191. })
  192. return
  193. }
  194. case "ApiInfo":
  195. err = validateApiInfo(option.Value)
  196. if err != nil {
  197. c.JSON(http.StatusOK, gin.H{
  198. "success": false,
  199. "message": err.Error(),
  200. })
  201. return
  202. }
  203. }
  204. err = model.UpdateOption(option.Key, option.Value)
  205. if err != nil {
  206. c.JSON(http.StatusOK, gin.H{
  207. "success": false,
  208. "message": err.Error(),
  209. })
  210. return
  211. }
  212. c.JSON(http.StatusOK, gin.H{
  213. "success": true,
  214. "message": "",
  215. })
  216. return
  217. }