Files
verify/backend/internal/router/app/auth.go
T
admin ea8ffb6c74 fix: 订阅模式登录验证、永久会员类型区分、动态代码HTTP返回值修复、侧边栏滚动位置保持
- 修复订阅模式登录时错误检查余额的问题
- 区分无限余额和永久订阅两种永久会员类型
- 修复动态代码HTTP请求返回值在JS中无法正确访问的问题
- 添加侧边栏滚动位置保持功能
- 移除developer角色相关代码,统一使用admin
- 添加缺失的i18n翻译key
2026-05-01 16:39:31 +08:00

919 lines
29 KiB
Go

package app
import (
"encoding/json"
"fmt"
"log"
"math/rand"
"strings"
"time"
"verification-platform-backend/internal/database"
"verification-platform-backend/internal/model"
"verification-platform-backend/internal/service"
"verification-platform-backend/pkg/jwt"
"verification-platform-backend/pkg/response"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
)
func getDeviceType(c *gin.Context, clientDeviceType string) string {
validTypes := map[string]bool{
"android": true,
"ios": true,
"windows": true,
"mac": true,
"linux": true,
"web": true,
}
if clientDeviceType != "" && validTypes[clientDeviceType] {
return clientDeviceType
}
userAgent := c.GetHeader("User-Agent")
return parseDeviceTypeFromUserAgent(userAgent)
}
func parseDeviceTypeFromUserAgent(userAgent string) string {
if userAgent == "" {
return "unknown"
}
ua := strings.ToLower(userAgent)
switch {
case strings.Contains(ua, "android"):
return "android"
case strings.Contains(ua, "iphone") || strings.Contains(ua, "ipad") || strings.Contains(ua, "ipod"):
return "ios"
case strings.Contains(ua, "windows"):
return "windows"
case strings.Contains(ua, "macintosh") || strings.Contains(ua, "mac os x"):
return "mac"
case strings.Contains(ua, "linux"):
return "linux"
case strings.Contains(ua, "mozilla") || strings.Contains(ua, "webkit") || strings.Contains(ua, "chrome") || strings.Contains(ua, "safari"):
return "web"
default:
return "unknown"
}
}
func SetupAuthUserRoutes(r *gin.RouterGroup) {
r.POST("/register", handleAppRegister)
r.POST("/login", handleAppLogin)
r.POST("/send-email-code", handleAppSendEmailCode)
r.POST("/reset-password", handleAppResetPassword)
}
func handleAppSendEmailCode(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
Email string `json:"email" binding:"required,email"`
Purpose string `json:"purpose"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "请输入有效的邮箱地址")
return
}
if req.Purpose == "" {
req.Purpose = "register"
}
if req.Purpose == "register" {
if !app.EnableEmailVerify {
response.Error(c, 400, "该应用未启用邮箱验证")
return
}
} else if req.Purpose == "reset_password" {
if !app.EnablePasswordReset {
response.Error(c, 400, "该应用未启用密码重置功能")
return
}
}
var smtpConfig model.AppSMTPConfig
if err := database.DB.Where("application_id = ?", app.ID).First(&smtpConfig).Error; err != nil {
response.Error(c, 400, "应用未配置SMTP")
return
}
var template model.EmailTemplate
database.DB.Where("application_id = ? AND type = ? AND status = ?", app.ID, req.Purpose, "active").
Order("is_default DESC").First(&template)
code := generateVerifyCode()
expireAt := time.Now().Add(15 * time.Minute)
verifyCode := model.EmailVerifyCode{
ApplicationID: app.ID,
Email: req.Email,
Code: code,
Purpose: req.Purpose,
ExpiresAt: expireAt,
}
database.DB.Create(&verifyCode)
emailService := service.NewEmailService()
config := service.EmailConfig{
Host: smtpConfig.Host,
Port: smtpConfig.Port,
User: smtpConfig.User,
Password: smtpConfig.Password,
FromName: smtpConfig.FromName,
FromEmail: smtpConfig.FromEmail,
UseSSL: smtpConfig.UseSSL,
}
subject := fmt.Sprintf("验证码 - %s", app.Name)
content := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"></head>
<body style="font-family: Arial, sans-serif; padding: 20px; background-color: #f5f5f5;">
<div style="max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<h2 style="color: #333; margin-bottom: 20px;">邮箱验证</h2>
<p style="color: #666; line-height: 1.6;">您的验证码是:<strong style="font-size: 24px; color: #1890ff;">%s</strong></p>
<p style="color: #999; font-size: 12px;">验证码有效期为15分钟,请尽快使用。</p>
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
<p style="color: #999; font-size: 12px;">此邮件由 %s 系统自动发送,请勿回复。</p>
</div>
</body>
</html>
`, code, app.Name)
if template.ID > 0 {
subject = template.Subject
content = template.Content
}
if err := emailService.SendEmail(config, req.Email, subject, content); err != nil {
response.Error(c, 500, fmt.Sprintf("发送失败: %v", err))
return
}
response.Success(c, gin.H{
"message": "验证码已发送",
})
}
func generateVerifyCode() string {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
return fmt.Sprintf("%06d", r.Intn(1000000))
}
func handleAppRegister(c *gin.Context) {
appKey := c.Param("appKey")
log.Printf("[DEBUG] Starting registration for appKey: %s", appKey)
app, exists := c.Get("app")
if !exists {
var appModel model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&appModel).Error; err != nil {
log.Printf("[DEBUG] Application not found for appKey: %s, error: %v", appKey, err)
response.Error(c, 404, "应用不存在")
return
}
app = &appModel
}
appModel := app.(*model.Application)
log.Printf("[DEBUG] Found application: ID=%d, Status=%s", appModel.ID, appModel.Status)
if service.GetApplicationDisabledStatus(appModel.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
var req struct {
Username string `json:"username"`
Email string `json:"email"`
EmailCode string `json:"email_code"`
Password string `json:"password"`
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
DeviceType string `json:"device_type"`
InstanceID string `json:"instance_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
log.Printf("[DEBUG] Invalid request parameters: %v", err)
response.Error(c, 400, "参数错误")
return
}
log.Printf("[DEBUG] Registration request for username: %s", req.Username)
if blocked, reason := checkRiskControl(c, appModel.ID, req.DeviceID, req.Username); blocked {
log.Printf("[DEBUG] Registration blocked by risk control: %s", reason)
service.LogVerification(c, &appModel.ID, nil, "register_blocked", "注册被拦截: "+reason, req.DeviceID, fmt.Errorf(reason))
response.Error(c, 403, reason)
return
}
if !appModel.AllowRegister {
log.Printf("[DEBUG] Registration disabled for app: %s", appKey)
response.Error(c, 403, "注册功能已关闭")
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"}
}
} 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
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, appModel.ID).First(&user).Error; err == nil {
log.Printf("[DEBUG] User already exists: %s", req.Username)
service.LogVerification(c, &appModel.ID, nil, "register_failed", "注册失败: 用户已存在 - "+req.Username, req.DeviceID, fmt.Errorf("用户已存在"))
response.Error(c, 400, "用户已存在")
return
}
user = model.AppUser{
Username: req.Username,
Email: req.Email,
Password: req.Password,
DeviceID: req.DeviceID,
Avatar: "",
Status: "active",
ApplicationID: appModel.ID,
IsTrialUser: appModel.EnableTrial,
}
if appModel.EnableTrial {
now := time.Now()
user.TrialStartAt = &now
if appModel.TrialBalance > 0 {
user.Balance = appModel.TrialBalance
}
}
if err := database.DB.Create(&user).Error; err != nil {
log.Printf("[DEBUG] Failed to create user: %v", err)
response.Error(c, 500, "注册失败")
return
}
log.Printf("[DEBUG] Successfully created user with ID: %d", user.ID)
if req.DeviceID != "" {
now := time.Now()
deviceType := getDeviceType(c, req.DeviceType)
deviceName := req.DeviceName
if deviceName == "" {
deviceName = req.DeviceID
}
var device model.UserDevice
if err := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", user.ID, appModel.ID, req.DeviceID).First(&device).Error; err != nil {
device = model.UserDevice{
UserID: user.ID,
ApplicationID: appModel.ID,
DeviceID: req.DeviceID,
DeviceName: deviceName,
DeviceType: deviceType,
Status: "active",
}
if err := database.DB.Create(&device).Error; err != nil {
log.Printf("[DEBUG] Failed to create device: %v", err)
} else {
log.Printf("[DEBUG] Successfully created device with ID: %d, type: %s", device.ID, deviceType)
}
}
instanceID := req.InstanceID
if instanceID == "" {
instanceID = req.DeviceID
}
if device.ID > 0 {
var session model.DeviceSession
sessionErr := database.DB.Where("device_id = ? AND instance_id = ?", device.ID, instanceID).First(&session).Error
if sessionErr != nil {
session = model.DeviceSession{
DeviceID: device.ID,
UserID: user.ID,
ApplicationID: appModel.ID,
InstanceID: instanceID,
LastHeartbeat: &now,
}
if err := database.DB.Create(&session).Error; err != nil {
log.Printf("[DEBUG] Failed to create session: %v", err)
}
} else {
session.LastHeartbeat = &now
if err := database.DB.Save(&session).Error; err != nil {
log.Printf("[DEBUG] Failed to update session: %v", err)
}
}
}
}
service.LogVerification(c, &appModel.ID, &user.ID, "register", "用户注册: "+req.Username, req.DeviceID, nil)
response.SuccessWithMessage(c, "注册成功", gin.H{
"user_id": user.ID,
})
}
func handleAppLogin(c *gin.Context) {
appKey := c.Param("appKey")
log.Printf("[DEBUG] Login attempt for appKey: %s", appKey)
app, exists := c.Get("app")
if !exists {
var appModel model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&appModel).Error; err != nil {
log.Printf("[DEBUG] Application not found for appKey: %s, error: %v", appKey, err)
response.Error(c, 404, "应用不存在")
return
}
app = &appModel
}
appModel := app.(*model.Application)
log.Printf("[DEBUG] Found application: ID=%d, Status=%s", appModel.ID, appModel.Status)
var req struct {
Username string `json:"username"`
Password string `json:"password"`
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
DeviceType string `json:"device_type"`
InstanceID string `json:"instance_id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
log.Printf("[DEBUG] Invalid request parameters: %v", err)
response.Error(c, 400, "参数错误")
return
}
log.Printf("[DEBUG] Login request for username: %s", req.Username)
if req.DeviceID == "" {
response.Error(c, 400, "设备ID不能为空")
return
}
var user model.AppUser
if err := database.DB.Where("username = ? AND application_id = ?", req.Username, appModel.ID).First(&user).Error; err != nil {
log.Printf("[DEBUG] User not found: %v", err)
service.LogVerification(c, &appModel.ID, nil, "login_failed", "登录失败: 用户不存在 - "+req.Username, req.DeviceID, fmt.Errorf("用户不存在"))
response.Error(c, 404, "用户不存在")
return
}
if user.Password != req.Password {
log.Printf("[DEBUG] Password mismatch for user: %s", req.Username)
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 密码错误 - "+req.Username, req.DeviceID, fmt.Errorf("密码错误"))
response.Error(c, 400, "密码错误")
return
}
if blocked, reason := checkRiskControl(c, appModel.ID, req.DeviceID, req.Username); blocked {
log.Printf("[DEBUG] Login blocked by risk control: %s", reason)
service.LogVerification(c, &appModel.ID, &user.ID, "login_blocked", "登录被拦截: "+reason, req.DeviceID, fmt.Errorf(reason))
response.Error(c, 403, reason)
return
}
if appModel.LoginPolicy == "strict" {
log.Printf("[DEBUG] LoginPolicy is strict, checking free period and trial")
isInFreePeriod := false
if appModel.EnableFreePeriod {
isInFreePeriod = checkFreePeriod(appModel)
}
log.Printf("[DEBUG] isInFreePeriod=%v", isInFreePeriod)
if !isInFreePeriod {
log.Printf("[DEBUG] Not in free period, checking trial")
isTrialValid := false
if user.IsTrialUser && user.TrialEndAt != nil && user.TrialEndAt.After(time.Now()) {
isTrialValid = true
}
log.Printf("[DEBUG] isTrialValid=%v, IsTrialUser=%v, TrialEndAt=%v, Balance=%f", isTrialValid, user.IsTrialUser, user.TrialEndAt, user.Balance)
if !isTrialValid {
if appModel.BillingType != "free" {
if user.Balance == -1 {
log.Printf("[DEBUG] User %d is permanent member, allowing login", user.ID)
} else if appModel.BillingType == "subscription" {
if user.ExpiryAt == nil || user.ExpiryAt.Before(time.Now()) {
log.Printf("[DEBUG] User %d subscription expired, ExpiryAt=%v", user.ID, user.ExpiryAt)
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 订阅已过期 - "+req.Username, req.DeviceID, fmt.Errorf("订阅已过期,请充值后继续使用"))
response.Error(c, 403, "订阅已过期,请充值后继续使用")
return
}
} else {
if user.Balance <= 0 {
log.Printf("[DEBUG] User %d has no balance remaining", user.ID)
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 余额不足 - "+req.Username, req.DeviceID, fmt.Errorf("余额不足,请充值后继续使用"))
response.Error(c, 403, "余额不足,请充值后继续使用")
return
}
}
}
}
} else {
log.Printf("[DEBUG] User is in free period, allowing login")
}
}
if req.DeviceID != "" {
heartbeatTimeout := time.Duration(appModel.HeartbeatTimeout) * time.Second
timeoutThreshold := time.Now().Add(-heartbeatTimeout)
database.DB.Where("user_id = ? AND application_id = ? AND last_heartbeat < ?", user.ID, appModel.ID, timeoutThreshold).Delete(&model.DeviceSession{})
now := time.Now()
deviceType := getDeviceType(c, req.DeviceType)
deviceName := req.DeviceName
if deviceName == "" {
deviceName = req.DeviceID
}
clientIP := c.ClientIP()
if appModel.BindType == "ip" || appModel.BindType == "mixed" {
var userIP model.UserIP
ipErr := database.DB.Where("user_id = ? AND application_id = ? AND ip_address = ?", user.ID, appModel.ID, clientIP).First(&userIP).Error
if ipErr != nil {
var ipCount int64
database.DB.Model(&model.UserIP{}).
Where("user_id = ? AND application_id = ?", user.ID, appModel.ID).
Count(&ipCount)
if appModel.MaxDevices > 0 && ipCount >= int64(appModel.MaxDevices) {
log.Printf("[DEBUG] IP limit exceeded for user %d: %d/%d", user.ID, ipCount, appModel.MaxDevices)
var userIPs []model.UserIP
database.DB.Where("user_id = ? AND application_id = ?", user.ID, appModel.ID).Order("created_at DESC").Find(&userIPs)
ipList := make([]gin.H, 0)
for _, ip := range userIPs {
ipList = append(ipList, gin.H{
"id": ip.ID,
"ip_address": ip.IPAddress,
"status": ip.Status,
"created_at": ip.CreatedAt,
})
}
response.ErrorWithData(c, 403, "IP绑定数量已达上限,请解绑后再试", gin.H{
"error_code": "IP_LIMIT_EXCEEDED",
"max_ips": appModel.MaxDevices,
"ip_count": ipCount,
"ips": ipList,
})
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: IP绑定数量已达上限 - "+req.Username, req.DeviceID, fmt.Errorf("IP绑定数量已达上限"))
return
}
userIP = model.UserIP{
UserID: user.ID,
ApplicationID: appModel.ID,
IPAddress: clientIP,
Status: "active",
}
if err := database.DB.Create(&userIP).Error; err != nil {
log.Printf("[DEBUG] Failed to create user IP: %v", err)
} else {
log.Printf("[DEBUG] Successfully created user IP with ID: %d, IP: %s", userIP.ID, clientIP)
}
}
}
var device model.UserDevice
deviceErr := database.DB.Where("user_id = ? AND application_id = ? AND device_id = ?", user.ID, appModel.ID, req.DeviceID).First(&device).Error
if deviceErr != nil {
if (appModel.BindType == "device" || appModel.BindType == "mixed") && appModel.MaxDevices > 0 {
var deviceCount int64
database.DB.Model(&model.UserDevice{}).
Where("user_id = ? AND application_id = ?", user.ID, appModel.ID).
Count(&deviceCount)
if deviceCount >= int64(appModel.MaxDevices) {
log.Printf("[DEBUG] Device limit exceeded for user %d: %d/%d", user.ID, deviceCount, appModel.MaxDevices)
var devices []model.UserDevice
database.DB.Where("user_id = ? AND application_id = ?", user.ID, appModel.ID).Order("created_at DESC").Find(&devices)
deviceList := make([]gin.H, 0)
for _, d := range devices {
var onlineSessions []model.DeviceSession
database.DB.Where("device_id = ? AND last_heartbeat > ?", d.ID, timeoutThreshold).Find(&onlineSessions)
deviceList = append(deviceList, gin.H{
"id": d.ID,
"device_id": d.DeviceID,
"device_name": d.DeviceName,
"device_type": d.DeviceType,
"online_count": len(onlineSessions),
"created_at": d.CreatedAt,
})
}
response.ErrorWithData(c, 403, "设备绑定数量已达上限,请解绑后再试", gin.H{
"error_code": "DEVICE_LIMIT_EXCEEDED",
"max_devices": appModel.MaxDevices,
"device_count": deviceCount,
"devices": deviceList,
})
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 设备绑定数量已达上限 - "+req.Username, req.DeviceID, fmt.Errorf("设备绑定数量已达上限"))
return
}
}
device = model.UserDevice{
UserID: user.ID,
ApplicationID: appModel.ID,
DeviceID: req.DeviceID,
DeviceName: deviceName,
DeviceType: deviceType,
Status: "active",
}
if err := database.DB.Create(&device).Error; err != nil {
log.Printf("[DEBUG] Failed to create device: %v", err)
} else {
log.Printf("[DEBUG] Successfully created device with ID: %d, type: %s", device.ID, deviceType)
}
}
instanceID := req.InstanceID
if instanceID == "" {
instanceID = req.DeviceID
}
if device.ID > 0 {
if appModel.MultiOpen && 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,
})
service.LogVerification(c, &appModel.ID, &user.ID, "login_failed", "登录失败: 多开数量已达上限 - "+req.Username, req.DeviceID, fmt.Errorf("多开数量已达上限"))
return
}
}
}
var session model.DeviceSession
if err := database.DB.Where("device_id = ? AND instance_id = ?", device.ID, instanceID).First(&session).Error; err != nil {
session = model.DeviceSession{
DeviceID: device.ID,
UserID: user.ID,
ApplicationID: appModel.ID,
InstanceID: instanceID,
LastHeartbeat: &now,
}
if err := database.DB.Create(&session).Error; err != nil {
log.Printf("[DEBUG] Failed to create session: %v", err)
} else {
log.Printf("[DEBUG] Successfully created session with ID: %d, instance: %s", session.ID, instanceID)
}
} else {
session.LastHeartbeat = &now
if err := database.DB.Save(&session).Error; err != nil {
log.Printf("[DEBUG] Failed to update session: %v", err)
} else {
log.Printf("[DEBUG] Successfully updated session %d last_heartbeat", session.ID)
}
}
}
}
token, err := jwt.GenerateToken(user.ID, user.Username, "app_user")
if err != nil {
log.Printf("[DEBUG] Failed to generate token: %v", err)
}
now := time.Now()
database.DB.Model(&user).Updates(map[string]interface{}{
"last_login_at": now,
"last_heartbeat_at": now,
})
service.LogVerification(c, &appModel.ID, &user.ID, "login", "用户登录: "+req.Username, req.DeviceID, nil)
response.SuccessWithMessage(c, "登录成功", gin.H{
"user_id": user.ID,
"token": token,
})
}
func checkFreePeriod(app *model.Application) bool {
now := time.Now()
log.Printf("[DEBUG] checkFreePeriod: EnableFreePeriod=%v, FreePeriodType=%s, FreePeriodStart=%s, FreePeriodEnd=%s, FreePeriodWeekdays=%s, FreePeriodStartTime=%s, FreePeriodEndTime=%s",
app.EnableFreePeriod, app.FreePeriodType, app.FreePeriodStart, app.FreePeriodEnd, app.FreePeriodWeekdays, app.FreePeriodStartTime, app.FreePeriodEndTime)
localLocation := time.Local
if app.FreePeriodType == "range" {
if app.FreePeriodStart == "" || app.FreePeriodEnd == "" {
log.Printf("[DEBUG] checkFreePeriod: range type but missing start or end")
return false
}
var startDate, endDate time.Time
var err error
if len(app.FreePeriodStart) > 10 {
startDate, err = time.ParseInLocation("2006-01-02T15:04", app.FreePeriodStart, localLocation)
} else {
startDate, err = time.ParseInLocation("2006-01-02", app.FreePeriodStart, localLocation)
}
if err != nil {
log.Printf("[DEBUG] checkFreePeriod: failed to parse start date: %v", err)
return false
}
if len(app.FreePeriodEnd) > 10 {
endDate, err = time.ParseInLocation("2006-01-02T15:04", app.FreePeriodEnd, localLocation)
} else {
endDate, err = time.ParseInLocation("2006-01-02", app.FreePeriodEnd, localLocation)
if err == nil {
endDate = endDate.Add(24 * time.Hour)
}
}
if err != nil {
log.Printf("[DEBUG] checkFreePeriod: failed to parse end date: %v", err)
return false
}
result := now.After(startDate) && now.Before(endDate)
log.Printf("[DEBUG] checkFreePeriod: range check result=%v, now=%v, start=%v, end=%v", result, now, startDate, endDate)
return result
} else if app.FreePeriodType == "weekdays" {
if app.FreePeriodWeekdays == "" {
log.Printf("[DEBUG] checkFreePeriod: weekdays type but missing weekdays")
return false
}
weekday := int(now.Weekday())
if weekday == 0 {
weekday = 7
}
weekdayStr := string(rune('0' + weekday))
if !contains(app.FreePeriodWeekdays, weekdayStr) {
log.Printf("[DEBUG] checkFreePeriod: weekday %s not in %s", weekdayStr, app.FreePeriodWeekdays)
return false
}
if app.FreePeriodStartTime != "" && app.FreePeriodEndTime != "" {
currentTime := now.Format("15:04")
result := currentTime >= app.FreePeriodStartTime && currentTime <= app.FreePeriodEndTime
log.Printf("[DEBUG] checkFreePeriod: weekday time check result=%v, currentTime=%s, startTime=%s, endTime=%s", result, currentTime, app.FreePeriodStartTime, app.FreePeriodEndTime)
return result
}
log.Printf("[DEBUG] checkFreePeriod: weekday check passed")
return true
}
log.Printf("[DEBUG] checkFreePeriod: unknown type %s", app.FreePeriodType)
return false
}
func checkRiskControl(c *gin.Context, appID uint, deviceID string, username string) (bool, string) {
clientIP := c.ClientIP()
var globalRules []model.RiskControlRule
database.DB.Where("application_id IS NULL AND status = ?", "active").Find(&globalRules)
var appRules []model.RiskControlRule
database.DB.Where("application_id = ? AND status = ?", appID, "active").Find(&appRules)
now := time.Now()
for _, rule := range globalRules {
if rule.ExpiresAt != nil && rule.ExpiresAt.Before(now) {
continue
}
switch rule.Type {
case "device":
if deviceID != "" && rule.Value == deviceID {
return true, "设备已被全局封禁: " + rule.Reason
}
case "ip":
if rule.Value == clientIP {
return true, "IP已被全局封禁: " + rule.Reason
}
case "region":
}
}
for _, rule := range appRules {
if rule.ExpiresAt != nil && rule.ExpiresAt.Before(now) {
continue
}
switch rule.Type {
case "device":
if deviceID != "" && rule.Value == deviceID {
return true, "设备已被封禁: " + rule.Reason
}
case "ip":
if rule.Value == clientIP {
return true, "IP已被封禁: " + rule.Reason
}
case "user":
if username != "" && rule.Value == username {
return true, "账号已被封禁: " + rule.Reason
}
case "region":
}
}
return false, ""
}
func contains(s string, substr string) bool {
for i := 0; i < len(s); i++ {
if s[i:i+1] == substr {
return true
}
}
return false
}
func containsString(slice []string, str string) bool {
for _, v := range slice {
if v == str {
return true
}
}
return false
}
func checkAppEmailPermission(userID uint, permission *model.PackagePermission) bool {
var userPackage model.UserPackage
if err := database.DB.Where("user_id = ? AND status = ?", userID, "active").
Preload("Package").First(&userPackage).Error; err != nil {
return false
}
if userPackage.ExpiredAt != nil && userPackage.ExpiredAt.Before(time.Now()) {
return false
}
if err := database.DB.Where("package_id = ?", userPackage.PackageID).First(permission).Error; err != nil {
return false
}
return true
}
func handleAppResetPassword(c *gin.Context) {
appKey := c.Param("appKey")
var app model.Application
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
response.Error(c, 404, "应用不存在")
return
}
if service.GetApplicationDisabledStatus(app.ID) {
response.Error(c, 403, "该应用已被禁用")
return
}
if !app.EnablePasswordReset {
response.Error(c, 400, "该应用未启用密码重置功能")
return
}
var req struct {
Email string `json:"email" binding:"required,email"`
Code string `json:"code" binding:"required"`
Password string `json:"password" binding:"required,min=6"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误")
return
}
var verifyCode model.EmailVerifyCode
if err := database.DB.Where(
"application_id = ? AND email = ? AND code = ? AND purpose = ? AND used = ?",
app.ID, req.Email, req.Code, "reset_password", false,
).First(&verifyCode).Error; err != nil {
response.Error(c, 400, "验证码无效或已过期")
return
}
if verifyCode.ExpiresAt.Before(time.Now()) {
response.Error(c, 400, "验证码已过期")
return
}
var user model.AppUser
if err := database.DB.Where("email = ? AND application_id = ?", req.Email, app.ID).First(&user).Error; err != nil {
response.Error(c, 404, "用户不存在")
return
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
response.Error(c, 500, "密码加密失败")
return
}
user.Password = string(hashedPassword)
if err := database.DB.Save(&user).Error; err != nil {
response.Error(c, 500, "密码重置失败")
return
}
verifyCode.Used = true
database.DB.Save(&verifyCode)
response.Success(c, gin.H{
"message": "密码重置成功",
})
}