| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- package controller
- import (
- "errors"
- "fmt"
- "net/http"
- "strconv"
- "github.com/QuantumNous/new-api/common"
- "github.com/QuantumNous/new-api/model"
- "github.com/gin-contrib/sessions"
- "github.com/gin-gonic/gin"
- )
- // Setup2FARequest 设置2FA请求结构
- type Setup2FARequest struct {
- Code string `json:"code" binding:"required"`
- }
- // Verify2FARequest 验证2FA请求结构
- type Verify2FARequest struct {
- Code string `json:"code" binding:"required"`
- }
- // Setup2FAResponse 设置2FA响应结构
- type Setup2FAResponse struct {
- Secret string `json:"secret"`
- QRCodeData string `json:"qr_code_data"`
- BackupCodes []string `json:"backup_codes"`
- }
- // Setup2FA 初始化2FA设置
- func Setup2FA(c *gin.Context) {
- userId := c.GetInt("id")
- // 检查用户是否已经启用2FA
- existing, err := model.GetTwoFAByUserId(userId)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- if existing != nil && existing.IsEnabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "用户已启用2FA,请先禁用后重新设置",
- })
- return
- }
- // 如果存在已禁用的2FA记录,先删除它
- if existing != nil && !existing.IsEnabled {
- if err := existing.Delete(); err != nil {
- common.ApiError(c, err)
- return
- }
- existing = nil // 重置为nil,后续将创建新记录
- }
- // 获取用户信息
- user, err := model.GetUserById(userId, false)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- // 生成TOTP密钥
- key, err := common.GenerateTOTPSecret(user.Username)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "生成2FA密钥失败",
- })
- common.SysLog("生成TOTP密钥失败: " + err.Error())
- return
- }
- // 生成备用码
- backupCodes, err := common.GenerateBackupCodes()
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "生成备用码失败",
- })
- common.SysLog("生成备用码失败: " + err.Error())
- return
- }
- // 生成二维码数据
- qrCodeData := common.GenerateQRCodeData(key.Secret(), user.Username)
- // 创建或更新2FA记录(暂未启用)
- twoFA := &model.TwoFA{
- UserId: userId,
- Secret: key.Secret(),
- IsEnabled: false,
- }
- if existing != nil {
- // 更新现有记录
- twoFA.Id = existing.Id
- err = twoFA.Update()
- } else {
- // 创建新记录
- err = twoFA.Create()
- }
- if err != nil {
- common.ApiError(c, err)
- return
- }
- // 创建备用码记录
- if err := model.CreateBackupCodes(userId, backupCodes); err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "保存备用码失败",
- })
- common.SysLog("保存备用码失败: " + err.Error())
- return
- }
- // 记录操作日志
- model.RecordLog(userId, model.LogTypeSystem, "开始设置两步验证")
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "2FA设置初始化成功,请使用认证器扫描二维码并输入验证码完成设置",
- "data": Setup2FAResponse{
- Secret: key.Secret(),
- QRCodeData: qrCodeData,
- BackupCodes: backupCodes,
- },
- })
- }
- // Enable2FA 启用2FA
- func Enable2FA(c *gin.Context) {
- var req Setup2FARequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "参数错误",
- })
- return
- }
- userId := c.GetInt("id")
- // 获取2FA记录
- twoFA, err := model.GetTwoFAByUserId(userId)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- if twoFA == nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "请先完成2FA初始化设置",
- })
- return
- }
- if twoFA.IsEnabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "2FA已经启用",
- })
- return
- }
- // 验证TOTP验证码
- cleanCode, err := common.ValidateNumericCode(req.Code)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- if !common.ValidateTOTPCode(twoFA.Secret, cleanCode) {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "验证码或备用码错误,请重试",
- })
- return
- }
- // 启用2FA
- if err := twoFA.Enable(); err != nil {
- common.ApiError(c, err)
- return
- }
- // 记录操作日志
- model.RecordLog(userId, model.LogTypeSystem, "成功启用两步验证")
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "两步验证启用成功",
- })
- }
- // Disable2FA 禁用2FA
- func Disable2FA(c *gin.Context) {
- var req Verify2FARequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "参数错误",
- })
- return
- }
- userId := c.GetInt("id")
- // 获取2FA记录
- twoFA, err := model.GetTwoFAByUserId(userId)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- if twoFA == nil || !twoFA.IsEnabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "用户未启用2FA",
- })
- return
- }
- // 验证TOTP验证码或备用码
- cleanCode, err := common.ValidateNumericCode(req.Code)
- isValidTOTP := false
- isValidBackup := false
- if err == nil {
- // 尝试验证TOTP
- isValidTOTP, _ = twoFA.ValidateTOTPAndUpdateUsage(cleanCode)
- }
- if !isValidTOTP {
- // 尝试验证备用码
- isValidBackup, err = twoFA.ValidateBackupCodeAndUpdateUsage(req.Code)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- }
- if !isValidTOTP && !isValidBackup {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "验证码或备用码错误,请重试",
- })
- return
- }
- // 禁用2FA
- if err := model.DisableTwoFA(userId); err != nil {
- common.ApiError(c, err)
- return
- }
- // 记录操作日志
- model.RecordLog(userId, model.LogTypeSystem, "禁用两步验证")
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "两步验证已禁用",
- })
- }
- // Get2FAStatus 获取用户2FA状态
- func Get2FAStatus(c *gin.Context) {
- userId := c.GetInt("id")
- twoFA, err := model.GetTwoFAByUserId(userId)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- status := map[string]interface{}{
- "enabled": false,
- "locked": false,
- }
- if twoFA != nil {
- status["enabled"] = twoFA.IsEnabled
- status["locked"] = twoFA.IsLocked()
- if twoFA.IsEnabled {
- // 获取剩余备用码数量
- backupCount, err := model.GetUnusedBackupCodeCount(userId)
- if err != nil {
- common.SysLog("获取备用码数量失败: " + err.Error())
- } else {
- status["backup_codes_remaining"] = backupCount
- }
- }
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": status,
- })
- }
- // RegenerateBackupCodes 重新生成备用码
- func RegenerateBackupCodes(c *gin.Context) {
- var req Verify2FARequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "参数错误",
- })
- return
- }
- userId := c.GetInt("id")
- // 获取2FA记录
- twoFA, err := model.GetTwoFAByUserId(userId)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- if twoFA == nil || !twoFA.IsEnabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "用户未启用2FA",
- })
- return
- }
- // 验证TOTP验证码
- cleanCode, err := common.ValidateNumericCode(req.Code)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- valid, err := twoFA.ValidateTOTPAndUpdateUsage(cleanCode)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- if !valid {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "验证码或备用码错误,请重试",
- })
- return
- }
- // 生成新的备用码
- backupCodes, err := common.GenerateBackupCodes()
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "生成备用码失败",
- })
- common.SysLog("生成备用码失败: " + err.Error())
- return
- }
- // 保存新的备用码
- if err := model.CreateBackupCodes(userId, backupCodes); err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "保存备用码失败",
- })
- common.SysLog("保存备用码失败: " + err.Error())
- return
- }
- // 记录操作日志
- model.RecordLog(userId, model.LogTypeSystem, "重新生成两步验证备用码")
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "备用码重新生成成功",
- "data": map[string]interface{}{
- "backup_codes": backupCodes,
- },
- })
- }
- // Verify2FALogin 登录时验证2FA
- func Verify2FALogin(c *gin.Context) {
- var req Verify2FARequest
- if err := c.ShouldBindJSON(&req); err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "参数错误",
- })
- return
- }
- // 从会话中获取pending用户信息
- session := sessions.Default(c)
- pendingUserId := session.Get("pending_user_id")
- if pendingUserId == nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "会话已过期,请重新登录",
- })
- return
- }
- userId, ok := pendingUserId.(int)
- if !ok {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "会话数据无效,请重新登录",
- })
- return
- }
- // 获取用户信息
- user, err := model.GetUserById(userId, false)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "用户不存在",
- })
- return
- }
- // 获取2FA记录
- twoFA, err := model.GetTwoFAByUserId(user.Id)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- if twoFA == nil || !twoFA.IsEnabled {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "用户未启用2FA",
- })
- return
- }
- // 验证TOTP验证码或备用码
- cleanCode, err := common.ValidateNumericCode(req.Code)
- isValidTOTP := false
- isValidBackup := false
- if err == nil {
- // 尝试验证TOTP
- isValidTOTP, _ = twoFA.ValidateTOTPAndUpdateUsage(cleanCode)
- }
- if !isValidTOTP {
- // 尝试验证备用码
- isValidBackup, err = twoFA.ValidateBackupCodeAndUpdateUsage(req.Code)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
- return
- }
- }
- if !isValidTOTP && !isValidBackup {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "验证码或备用码错误,请重试",
- })
- return
- }
- // 2FA验证成功,清理pending会话信息并完成登录
- session.Delete("pending_username")
- session.Delete("pending_user_id")
- session.Save()
- setupLogin(user, c)
- }
- // Admin2FAStats 管理员获取2FA统计信息
- func Admin2FAStats(c *gin.Context) {
- stats, err := model.GetTwoFAStats()
- if err != nil {
- common.ApiError(c, err)
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": stats,
- })
- }
- // AdminDisable2FA 管理员强制禁用用户2FA
- func AdminDisable2FA(c *gin.Context) {
- userIdStr := c.Param("id")
- userId, err := strconv.Atoi(userIdStr)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "用户ID格式错误",
- })
- return
- }
- // 检查目标用户权限
- targetUser, err := model.GetUserById(userId, false)
- if err != nil {
- common.ApiError(c, err)
- return
- }
- myRole := c.GetInt("role")
- if myRole <= targetUser.Role && myRole != common.RoleRootUser {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "无权操作同级或更高级用户的2FA设置",
- })
- return
- }
- // 禁用2FA
- if err := model.DisableTwoFA(userId); err != nil {
- if errors.Is(err, model.ErrTwoFANotEnabled) {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": "用户未启用2FA",
- })
- return
- }
- common.ApiError(c, err)
- return
- }
- // 记录操作日志
- adminId := c.GetInt("id")
- model.RecordLog(userId, model.LogTypeManage,
- fmt.Sprintf("管理员(ID:%d)强制禁用了用户的两步验证", adminId))
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "用户2FA已被强制禁用",
- })
- }
|