twofa.go 12 KB

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