twofa.go 12 KB

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