twofa.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. package controller
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "github.com/QuantumNous/new-api/common"
  8. "github.com/QuantumNous/new-api/model"
  9. "github.com/gin-contrib/sessions"
  10. "github.com/gin-gonic/gin"
  11. )
  12. // Setup2FARequest 设置2FA请求结构
  13. type Setup2FARequest struct {
  14. Code string `json:"code" binding:"required"`
  15. }
  16. // Verify2FARequest 验证2FA请求结构
  17. type Verify2FARequest struct {
  18. Code string `json:"code" binding:"required"`
  19. }
  20. // Setup2FAResponse 设置2FA响应结构
  21. type Setup2FAResponse struct {
  22. Secret string `json:"secret"`
  23. QRCodeData string `json:"qr_code_data"`
  24. BackupCodes []string `json:"backup_codes"`
  25. }
  26. // Setup2FA 初始化2FA设置
  27. func Setup2FA(c *gin.Context) {
  28. userId := c.GetInt("id")
  29. // 检查用户是否已经启用2FA
  30. existing, err := model.GetTwoFAByUserId(userId)
  31. if err != nil {
  32. common.ApiError(c, err)
  33. return
  34. }
  35. if existing != nil && existing.IsEnabled {
  36. c.JSON(http.StatusOK, gin.H{
  37. "success": false,
  38. "message": "用户已启用2FA,请先禁用后重新设置",
  39. })
  40. return
  41. }
  42. // 如果存在已禁用的2FA记录,先删除它
  43. if existing != nil && !existing.IsEnabled {
  44. if err := existing.Delete(); err != nil {
  45. common.ApiError(c, err)
  46. return
  47. }
  48. existing = nil // 重置为nil,后续将创建新记录
  49. }
  50. // 获取用户信息
  51. user, err := model.GetUserById(userId, false)
  52. if err != nil {
  53. common.ApiError(c, err)
  54. return
  55. }
  56. // 生成TOTP密钥
  57. key, err := common.GenerateTOTPSecret(user.Username)
  58. if err != nil {
  59. c.JSON(http.StatusOK, gin.H{
  60. "success": false,
  61. "message": "生成2FA密钥失败",
  62. })
  63. common.SysLog("生成TOTP密钥失败: " + err.Error())
  64. return
  65. }
  66. // 生成备用码
  67. backupCodes, err := common.GenerateBackupCodes()
  68. if err != nil {
  69. c.JSON(http.StatusOK, gin.H{
  70. "success": false,
  71. "message": "生成备用码失败",
  72. })
  73. common.SysLog("生成备用码失败: " + err.Error())
  74. return
  75. }
  76. // 生成二维码数据
  77. qrCodeData := common.GenerateQRCodeData(key.Secret(), user.Username)
  78. // 创建或更新2FA记录(暂未启用)
  79. twoFA := &model.TwoFA{
  80. UserId: userId,
  81. Secret: key.Secret(),
  82. IsEnabled: false,
  83. }
  84. if existing != nil {
  85. // 更新现有记录
  86. twoFA.Id = existing.Id
  87. err = twoFA.Update()
  88. } else {
  89. // 创建新记录
  90. err = twoFA.Create()
  91. }
  92. if err != nil {
  93. common.ApiError(c, err)
  94. return
  95. }
  96. // 创建备用码记录
  97. if err := model.CreateBackupCodes(userId, backupCodes); err != nil {
  98. c.JSON(http.StatusOK, gin.H{
  99. "success": false,
  100. "message": "保存备用码失败",
  101. })
  102. common.SysLog("保存备用码失败: " + err.Error())
  103. return
  104. }
  105. // 记录操作日志
  106. model.RecordLog(userId, model.LogTypeSystem, "开始设置两步验证")
  107. c.JSON(http.StatusOK, gin.H{
  108. "success": true,
  109. "message": "2FA设置初始化成功,请使用认证器扫描二维码并输入验证码完成设置",
  110. "data": Setup2FAResponse{
  111. Secret: key.Secret(),
  112. QRCodeData: qrCodeData,
  113. BackupCodes: backupCodes,
  114. },
  115. })
  116. }
  117. // Enable2FA 启用2FA
  118. func Enable2FA(c *gin.Context) {
  119. var req Setup2FARequest
  120. if err := c.ShouldBindJSON(&req); err != nil {
  121. c.JSON(http.StatusOK, gin.H{
  122. "success": false,
  123. "message": "参数错误",
  124. })
  125. return
  126. }
  127. userId := c.GetInt("id")
  128. // 获取2FA记录
  129. twoFA, err := model.GetTwoFAByUserId(userId)
  130. if err != nil {
  131. common.ApiError(c, err)
  132. return
  133. }
  134. if twoFA == nil {
  135. c.JSON(http.StatusOK, gin.H{
  136. "success": false,
  137. "message": "请先完成2FA初始化设置",
  138. })
  139. return
  140. }
  141. if twoFA.IsEnabled {
  142. c.JSON(http.StatusOK, gin.H{
  143. "success": false,
  144. "message": "2FA已经启用",
  145. })
  146. return
  147. }
  148. // 验证TOTP验证码
  149. cleanCode, err := common.ValidateNumericCode(req.Code)
  150. if err != nil {
  151. c.JSON(http.StatusOK, gin.H{
  152. "success": false,
  153. "message": err.Error(),
  154. })
  155. return
  156. }
  157. if !common.ValidateTOTPCode(twoFA.Secret, cleanCode) {
  158. c.JSON(http.StatusOK, gin.H{
  159. "success": false,
  160. "message": "验证码或备用码错误,请重试",
  161. })
  162. return
  163. }
  164. // 启用2FA
  165. if err := twoFA.Enable(); err != nil {
  166. common.ApiError(c, err)
  167. return
  168. }
  169. // 记录操作日志
  170. model.RecordLog(userId, model.LogTypeSystem, "成功启用两步验证")
  171. c.JSON(http.StatusOK, gin.H{
  172. "success": true,
  173. "message": "两步验证启用成功",
  174. })
  175. }
  176. // Disable2FA 禁用2FA
  177. func Disable2FA(c *gin.Context) {
  178. var req Verify2FARequest
  179. if err := c.ShouldBindJSON(&req); err != nil {
  180. c.JSON(http.StatusOK, gin.H{
  181. "success": false,
  182. "message": "参数错误",
  183. })
  184. return
  185. }
  186. userId := c.GetInt("id")
  187. // 获取2FA记录
  188. twoFA, err := model.GetTwoFAByUserId(userId)
  189. if err != nil {
  190. common.ApiError(c, err)
  191. return
  192. }
  193. if twoFA == nil || !twoFA.IsEnabled {
  194. c.JSON(http.StatusOK, gin.H{
  195. "success": false,
  196. "message": "用户未启用2FA",
  197. })
  198. return
  199. }
  200. // 验证TOTP验证码或备用码
  201. cleanCode, err := common.ValidateNumericCode(req.Code)
  202. isValidTOTP := false
  203. isValidBackup := false
  204. if err == nil {
  205. // 尝试验证TOTP
  206. isValidTOTP, _ = twoFA.ValidateTOTPAndUpdateUsage(cleanCode)
  207. }
  208. if !isValidTOTP {
  209. // 尝试验证备用码
  210. isValidBackup, err = twoFA.ValidateBackupCodeAndUpdateUsage(req.Code)
  211. if err != nil {
  212. c.JSON(http.StatusOK, gin.H{
  213. "success": false,
  214. "message": err.Error(),
  215. })
  216. return
  217. }
  218. }
  219. if !isValidTOTP && !isValidBackup {
  220. c.JSON(http.StatusOK, gin.H{
  221. "success": false,
  222. "message": "验证码或备用码错误,请重试",
  223. })
  224. return
  225. }
  226. // 禁用2FA
  227. if err := model.DisableTwoFA(userId); err != nil {
  228. common.ApiError(c, err)
  229. return
  230. }
  231. // 记录操作日志
  232. model.RecordLog(userId, model.LogTypeSystem, "禁用两步验证")
  233. c.JSON(http.StatusOK, gin.H{
  234. "success": true,
  235. "message": "两步验证已禁用",
  236. })
  237. }
  238. // Get2FAStatus 获取用户2FA状态
  239. func Get2FAStatus(c *gin.Context) {
  240. userId := c.GetInt("id")
  241. twoFA, err := model.GetTwoFAByUserId(userId)
  242. if err != nil {
  243. common.ApiError(c, err)
  244. return
  245. }
  246. status := map[string]interface{}{
  247. "enabled": false,
  248. "locked": false,
  249. }
  250. if twoFA != nil {
  251. status["enabled"] = twoFA.IsEnabled
  252. status["locked"] = twoFA.IsLocked()
  253. if twoFA.IsEnabled {
  254. // 获取剩余备用码数量
  255. backupCount, err := model.GetUnusedBackupCodeCount(userId)
  256. if err != nil {
  257. common.SysLog("获取备用码数量失败: " + err.Error())
  258. } else {
  259. status["backup_codes_remaining"] = backupCount
  260. }
  261. }
  262. }
  263. c.JSON(http.StatusOK, gin.H{
  264. "success": true,
  265. "message": "",
  266. "data": status,
  267. })
  268. }
  269. // RegenerateBackupCodes 重新生成备用码
  270. func RegenerateBackupCodes(c *gin.Context) {
  271. var req Verify2FARequest
  272. if err := c.ShouldBindJSON(&req); err != nil {
  273. c.JSON(http.StatusOK, gin.H{
  274. "success": false,
  275. "message": "参数错误",
  276. })
  277. return
  278. }
  279. userId := c.GetInt("id")
  280. // 获取2FA记录
  281. twoFA, err := model.GetTwoFAByUserId(userId)
  282. if err != nil {
  283. common.ApiError(c, err)
  284. return
  285. }
  286. if twoFA == nil || !twoFA.IsEnabled {
  287. c.JSON(http.StatusOK, gin.H{
  288. "success": false,
  289. "message": "用户未启用2FA",
  290. })
  291. return
  292. }
  293. // 验证TOTP验证码
  294. cleanCode, err := common.ValidateNumericCode(req.Code)
  295. if err != nil {
  296. c.JSON(http.StatusOK, gin.H{
  297. "success": false,
  298. "message": err.Error(),
  299. })
  300. return
  301. }
  302. valid, err := twoFA.ValidateTOTPAndUpdateUsage(cleanCode)
  303. if err != nil {
  304. c.JSON(http.StatusOK, gin.H{
  305. "success": false,
  306. "message": err.Error(),
  307. })
  308. return
  309. }
  310. if !valid {
  311. c.JSON(http.StatusOK, gin.H{
  312. "success": false,
  313. "message": "验证码或备用码错误,请重试",
  314. })
  315. return
  316. }
  317. // 生成新的备用码
  318. backupCodes, err := common.GenerateBackupCodes()
  319. if err != nil {
  320. c.JSON(http.StatusOK, gin.H{
  321. "success": false,
  322. "message": "生成备用码失败",
  323. })
  324. common.SysLog("生成备用码失败: " + err.Error())
  325. return
  326. }
  327. // 保存新的备用码
  328. if err := model.CreateBackupCodes(userId, backupCodes); err != nil {
  329. c.JSON(http.StatusOK, gin.H{
  330. "success": false,
  331. "message": "保存备用码失败",
  332. })
  333. common.SysLog("保存备用码失败: " + err.Error())
  334. return
  335. }
  336. // 记录操作日志
  337. model.RecordLog(userId, model.LogTypeSystem, "重新生成两步验证备用码")
  338. c.JSON(http.StatusOK, gin.H{
  339. "success": true,
  340. "message": "备用码重新生成成功",
  341. "data": map[string]interface{}{
  342. "backup_codes": backupCodes,
  343. },
  344. })
  345. }
  346. // Verify2FALogin 登录时验证2FA
  347. func Verify2FALogin(c *gin.Context) {
  348. var req Verify2FARequest
  349. if err := c.ShouldBindJSON(&req); err != nil {
  350. c.JSON(http.StatusOK, gin.H{
  351. "success": false,
  352. "message": "参数错误",
  353. })
  354. return
  355. }
  356. // 从会话中获取pending用户信息
  357. session := sessions.Default(c)
  358. pendingUserId := session.Get("pending_user_id")
  359. if pendingUserId == nil {
  360. c.JSON(http.StatusOK, gin.H{
  361. "success": false,
  362. "message": "会话已过期,请重新登录",
  363. })
  364. return
  365. }
  366. userId, ok := pendingUserId.(int)
  367. if !ok {
  368. c.JSON(http.StatusOK, gin.H{
  369. "success": false,
  370. "message": "会话数据无效,请重新登录",
  371. })
  372. return
  373. }
  374. // 获取用户信息
  375. user, err := model.GetUserById(userId, false)
  376. if err != nil {
  377. c.JSON(http.StatusOK, gin.H{
  378. "success": false,
  379. "message": "用户不存在",
  380. })
  381. return
  382. }
  383. // 获取2FA记录
  384. twoFA, err := model.GetTwoFAByUserId(user.Id)
  385. if err != nil {
  386. common.ApiError(c, err)
  387. return
  388. }
  389. if twoFA == nil || !twoFA.IsEnabled {
  390. c.JSON(http.StatusOK, gin.H{
  391. "success": false,
  392. "message": "用户未启用2FA",
  393. })
  394. return
  395. }
  396. // 验证TOTP验证码或备用码
  397. cleanCode, err := common.ValidateNumericCode(req.Code)
  398. isValidTOTP := false
  399. isValidBackup := false
  400. if err == nil {
  401. // 尝试验证TOTP
  402. isValidTOTP, _ = twoFA.ValidateTOTPAndUpdateUsage(cleanCode)
  403. }
  404. if !isValidTOTP {
  405. // 尝试验证备用码
  406. isValidBackup, err = twoFA.ValidateBackupCodeAndUpdateUsage(req.Code)
  407. if err != nil {
  408. c.JSON(http.StatusOK, gin.H{
  409. "success": false,
  410. "message": err.Error(),
  411. })
  412. return
  413. }
  414. }
  415. if !isValidTOTP && !isValidBackup {
  416. c.JSON(http.StatusOK, gin.H{
  417. "success": false,
  418. "message": "验证码或备用码错误,请重试",
  419. })
  420. return
  421. }
  422. // 2FA验证成功,清理pending会话信息并完成登录
  423. session.Delete("pending_username")
  424. session.Delete("pending_user_id")
  425. session.Save()
  426. setupLogin(user, c)
  427. }
  428. // Admin2FAStats 管理员获取2FA统计信息
  429. func Admin2FAStats(c *gin.Context) {
  430. stats, err := model.GetTwoFAStats()
  431. if err != nil {
  432. common.ApiError(c, err)
  433. return
  434. }
  435. c.JSON(http.StatusOK, gin.H{
  436. "success": true,
  437. "message": "",
  438. "data": stats,
  439. })
  440. }
  441. // AdminDisable2FA 管理员强制禁用用户2FA
  442. func AdminDisable2FA(c *gin.Context) {
  443. userIdStr := c.Param("id")
  444. userId, err := strconv.Atoi(userIdStr)
  445. if err != nil {
  446. c.JSON(http.StatusOK, gin.H{
  447. "success": false,
  448. "message": "用户ID格式错误",
  449. })
  450. return
  451. }
  452. // 检查目标用户权限
  453. targetUser, err := model.GetUserById(userId, false)
  454. if err != nil {
  455. common.ApiError(c, err)
  456. return
  457. }
  458. myRole := c.GetInt("role")
  459. if myRole <= targetUser.Role && myRole != common.RoleRootUser {
  460. c.JSON(http.StatusOK, gin.H{
  461. "success": false,
  462. "message": "无权操作同级或更高级用户的2FA设置",
  463. })
  464. return
  465. }
  466. // 禁用2FA
  467. if err := model.DisableTwoFA(userId); err != nil {
  468. if errors.Is(err, model.ErrTwoFANotEnabled) {
  469. c.JSON(http.StatusOK, gin.H{
  470. "success": false,
  471. "message": "用户未启用2FA",
  472. })
  473. return
  474. }
  475. common.ApiError(c, err)
  476. return
  477. }
  478. // 记录操作日志
  479. adminId := c.GetInt("id")
  480. model.RecordLog(userId, model.LogTypeManage,
  481. fmt.Sprintf("管理员(ID:%d)强制禁用了用户的两步验证", adminId))
  482. c.JSON(http.StatusOK, gin.H{
  483. "success": true,
  484. "message": "用户2FA已被强制禁用",
  485. })
  486. }