feat: 分离密码重置和修改密码功能
- 密码重置:用户忘记密码时通过邮箱/短信验证(需开启开关) - 修改密码:已登录用户通过旧密码修改(固定功能,无需配置) - 添加 EnablePasswordReset 开关控制密码重置功能 - PasswordResetMethod 只支持 email/sms 两种验证方式 - 添加短信配置管理功能 - 移除独立的应用邮箱设置页面
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
@@ -96,8 +95,8 @@ func handleAppSendEmailCode(c *gin.Context) {
|
||||
}
|
||||
|
||||
if req.Purpose == "register" {
|
||||
if !app.EnableEmailVerify {
|
||||
response.Error(c, 400, "该应用未启用邮箱验证")
|
||||
if !app.EnableRegisterVerify {
|
||||
response.Error(c, 400, "该应用未启用注册验证")
|
||||
return
|
||||
}
|
||||
} else if req.Purpose == "reset_password" {
|
||||
@@ -203,6 +202,8 @@ func handleAppRegister(c *gin.Context) {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
EmailCode string `json:"email_code"`
|
||||
Phone string `json:"phone"`
|
||||
SmsCode string `json:"sms_code"`
|
||||
Password string `json:"password"`
|
||||
DeviceID string `json:"device_id"`
|
||||
DeviceName string `json:"device_name"`
|
||||
@@ -229,59 +230,60 @@ func handleAppRegister(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var allowedMethods []string
|
||||
if appModel.RegisterMethods != "" {
|
||||
if err := json.Unmarshal([]byte(appModel.RegisterMethods), &allowedMethods); err != nil {
|
||||
log.Printf("[DEBUG] Failed to parse register methods: %v", err)
|
||||
allowedMethods = []string{"username"}
|
||||
if appModel.EnableRegisterVerify {
|
||||
if appModel.VerifyMethod == "email" {
|
||||
if req.Email == "" {
|
||||
response.Error(c, 400, "请输入邮箱地址")
|
||||
return
|
||||
}
|
||||
if req.EmailCode == "" {
|
||||
response.Error(c, 400, "请输入邮箱验证码")
|
||||
return
|
||||
}
|
||||
|
||||
var verifyCode model.EmailVerifyCode
|
||||
if err := database.DB.Where(
|
||||
"application_id = ? AND email = ? AND code = ? AND purpose = ? AND used = ?",
|
||||
appModel.ID, req.Email, req.EmailCode, "register", false,
|
||||
).First(&verifyCode).Error; err != nil {
|
||||
response.Error(c, 400, "验证码错误或已过期")
|
||||
return
|
||||
}
|
||||
|
||||
if verifyCode.ExpiresAt.Before(time.Now()) {
|
||||
response.Error(c, 400, "验证码已过期")
|
||||
return
|
||||
}
|
||||
|
||||
verifyCode.Used = true
|
||||
database.DB.Save(&verifyCode)
|
||||
} else if appModel.VerifyMethod == "sms" {
|
||||
if req.Phone == "" {
|
||||
response.Error(c, 400, "请输入手机号")
|
||||
return
|
||||
}
|
||||
if req.SmsCode == "" {
|
||||
response.Error(c, 400, "请输入短信验证码")
|
||||
return
|
||||
}
|
||||
|
||||
var verifyCode model.SmsVerifyCode
|
||||
if err := database.DB.Where(
|
||||
"application_id = ? AND phone = ? AND code = ? AND purpose = ? AND used = ?",
|
||||
appModel.ID, req.Phone, req.SmsCode, "register", false,
|
||||
).First(&verifyCode).Error; err != nil {
|
||||
response.Error(c, 400, "验证码错误或已过期")
|
||||
return
|
||||
}
|
||||
|
||||
if verifyCode.ExpiresAt.Before(time.Now()) {
|
||||
response.Error(c, 400, "验证码已过期")
|
||||
return
|
||||
}
|
||||
|
||||
verifyCode.Used = true
|
||||
database.DB.Save(&verifyCode)
|
||||
}
|
||||
} else {
|
||||
allowedMethods = []string{"username"}
|
||||
}
|
||||
|
||||
isEmailRegister := req.Email != "" && req.Username == ""
|
||||
isUsernameRegister := req.Username != "" && req.Email == ""
|
||||
isPhoneRegister := false
|
||||
|
||||
if isEmailRegister && !containsString(allowedMethods, "email") {
|
||||
response.Error(c, 403, "邮箱注册方式未开放")
|
||||
return
|
||||
}
|
||||
if isUsernameRegister && !containsString(allowedMethods, "username") {
|
||||
response.Error(c, 403, "用户名注册方式未开放")
|
||||
return
|
||||
}
|
||||
if isPhoneRegister && !containsString(allowedMethods, "phone") {
|
||||
response.Error(c, 403, "手机号注册方式未开放")
|
||||
return
|
||||
}
|
||||
|
||||
if appModel.EnableEmailVerify && appModel.RequireEmailVerify {
|
||||
if req.Email == "" {
|
||||
response.Error(c, 400, "请输入邮箱地址")
|
||||
return
|
||||
}
|
||||
if req.EmailCode == "" {
|
||||
response.Error(c, 400, "请输入邮箱验证码")
|
||||
return
|
||||
}
|
||||
|
||||
var verifyCode model.EmailVerifyCode
|
||||
if err := database.DB.Where(
|
||||
"application_id = ? AND email = ? AND code = ? AND purpose = ? AND used = ?",
|
||||
appModel.ID, req.Email, req.EmailCode, "register", false,
|
||||
).First(&verifyCode).Error; err != nil {
|
||||
response.Error(c, 400, "验证码错误或已过期")
|
||||
return
|
||||
}
|
||||
|
||||
if verifyCode.ExpiresAt.Before(time.Now()) {
|
||||
response.Error(c, 400, "验证码已过期")
|
||||
return
|
||||
}
|
||||
|
||||
verifyCode.Used = true
|
||||
database.DB.Save(&verifyCode)
|
||||
}
|
||||
|
||||
var user model.AppUser
|
||||
@@ -609,40 +611,38 @@ func handleAppLogin(c *gin.Context) {
|
||||
}
|
||||
|
||||
if device.ID > 0 {
|
||||
if appModel.MultiOpen && appModel.MaxInstances > 0 {
|
||||
if appModel.MultiOpenMode != "unlimited" && appModel.MaxInstances > 0 {
|
||||
var sessionCount int64
|
||||
database.DB.Model(&model.DeviceSession{}).
|
||||
Where("device_id = ? AND instance_id != ? AND last_heartbeat > ?", device.ID, instanceID, timeoutThreshold).
|
||||
Count(&sessionCount)
|
||||
|
||||
if sessionCount >= int64(appModel.MaxInstances) {
|
||||
if appModel.MultiOpenMode == "forbidden" {
|
||||
log.Printf("[DEBUG] Multi-instance limit exceeded for user %d device %s: %d/%d", user.ID, req.DeviceID, sessionCount, appModel.MaxInstances)
|
||||
|
||||
var sessions []model.DeviceSession
|
||||
database.DB.Where("device_id = ? AND instance_id != ?", device.ID, instanceID).Order("last_heartbeat DESC").Find(&sessions)
|
||||
|
||||
sessionList := make([]gin.H, 0)
|
||||
for _, s := range sessions {
|
||||
isOnline := s.LastHeartbeat != nil && s.LastHeartbeat.After(timeoutThreshold)
|
||||
sessionList = append(sessionList, gin.H{
|
||||
"id": s.ID,
|
||||
"instance_id": s.InstanceID,
|
||||
"is_online": isOnline,
|
||||
"last_heartbeat": s.LastHeartbeat,
|
||||
"created_at": s.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
response.ErrorWithData(c, 403, "多开数量已达上限", gin.H{
|
||||
"error_code": "MULTI_INSTANCE_LIMIT_EXCEEDED",
|
||||
"max_instances": appModel.MaxInstances,
|
||||
"instance_count": sessionCount,
|
||||
"instances": sessionList,
|
||||
log.Printf("[DEBUG] Multi-instance limit exceeded for user %d device %s: %d/%d", user.ID, req.DeviceID, sessionCount, appModel.MaxInstances)
|
||||
|
||||
var sessions []model.DeviceSession
|
||||
database.DB.Where("device_id = ? AND instance_id != ?", device.ID, instanceID).Order("last_heartbeat DESC").Find(&sessions)
|
||||
|
||||
sessionList := make([]gin.H, 0)
|
||||
for _, s := range sessions {
|
||||
isOnline := s.LastHeartbeat != nil && s.LastHeartbeat.After(timeoutThreshold)
|
||||
sessionList = append(sessionList, gin.H{
|
||||
"id": s.ID,
|
||||
"instance_id": s.InstanceID,
|
||||
"is_online": isOnline,
|
||||
"last_heartbeat": s.LastHeartbeat,
|
||||
"created_at": s.CreatedAt,
|
||||
})
|
||||
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 多开数量已达上限 - "+req.Username, req.DeviceID, fmt.Errorf("多开数量已达上限"))
|
||||
return
|
||||
}
|
||||
|
||||
response.ErrorWithData(c, 403, "多开数量已达上限", gin.H{
|
||||
"error_code": "MULTI_INSTANCE_LIMIT_EXCEEDED",
|
||||
"max_instances": appModel.MaxInstances,
|
||||
"instance_count": sessionCount,
|
||||
"instances": sessionList,
|
||||
})
|
||||
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 多开数量已达上限 - "+req.Username, req.DeviceID, fmt.Errorf("多开数量已达上限"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user