package admin import ( "crypto/tls" "encoding/json" "fmt" "github.com/bjdgyc/anylink/base" "github.com/bjdgyc/anylink/dbdata" "github.com/golang-jwt/jwt/v4" "net" "net/http" "net/smtp" "net/url" "strconv" "strings" "time" ) var forgot_interval_time = 1 * 60 // 1分钟的间隔时间(单位:秒) var reset_interval_time = 30 // 密码重置有效期(单位: 分钟) func GenerateResetToken(userID int) (string, error) { fmt.Println(base.Cfg.JwtSecret) token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": userID, "exp": time.Now().Add(time.Duration(reset_interval_time) * time.Minute).Unix(), }) return token.SignedString([]byte(base.Cfg.JwtSecret)) } type CustomClaims struct { UserID int `json:"user_id"` jwt.StandardClaims } func ValidateResetToken(tokenString string) (*CustomClaims, error) { token, err := jwt.ParseWithClaims( tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte(base.Cfg.JwtSecret), nil }, ) if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { return claims, nil } return nil, err } // 重置密码函数 func ResetPassword(w http.ResponseWriter, r *http.Request) { var req struct { Token string `json:"token" binding:"required"` Password string `json:"password" binding:"required"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { RespError(w, RespInternalErr, "json 解析失败") return } else { // 验证token,并获取UserID claims, valid_err := ValidateResetToken(req.Token) if valid_err != nil { msg := fmt.Sprintf("验证失败, 重置链接已过期, 请重新申请。错误信息: %v", valid_err) RespError(w, RespInternalErr, msg) return } // 根据验证后的UserId 来更新用户表的密码 s := &dbdata.User{PinCode: req.Password} update_err := dbdata.Update("Id", claims.UserID, s) if update_err != nil { RespError(w, RespInternalErr, "更新密码失败") return } fmt.Println("更新密码成功") // 删除重置记录表的数据 reset := dbdata.PasswordReset{UserId: claims.UserID} del_err := dbdata.Del(&reset) if del_err != nil { fmt.Println("删除记录失败", del_err) } else { fmt.Println("删除验证记录成功,UserId", claims.UserID) } RespSucess(w, "密码重置成功") } } func ForgotPassword(w http.ResponseWriter, r *http.Request) { // 校验 Content-Type if r.Header.Get("Content-Type") != "application/json" { RespError(w, RespInternalErr, "仅支持 JSON 格式") return } // 解析json数据 var req struct { Email string `json:"email"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { RespError(w, RespInternalErr, "Json 解析失败") return } // 获取用户数据 user := &dbdata.User{} err := dbdata.One("Email", req.Email, user) if err != nil { RespError(w, RespInternalErr, "用户不存在 输入的地址:"+req.Email) return } // 检查上一次请求的时间 reset := &dbdata.PasswordReset{} err = dbdata.One("user_id", user.Id, reset) if err != nil { if err == dbdata.ErrNotFound { base.Info("此账号没有重置记录") } else { RespError(w, RespInternalErr, "查询重置记录失败", err.Error()) return } } else { // 检查时间间隔是否足够 currentTime := int(time.Now().Unix()) lastRequestTime := reset.LastRequestTime if currentTime-lastRequestTime < forgot_interval_time { msg := fmt.Sprintf("重复的重置操作,请等待%d秒后进行重置申请", forgot_interval_time) RespError(w, RespInternalErr, msg) return } } // 生成新的重置令牌 token, err := GenerateResetToken(user.Id) if err != nil { RespError(w, RespInternalErr, "生成token失败") return } // 开始更新或插入重置记录 reset.ExpiresAt = int(time.Now().Add(time.Duration(reset_interval_time) * time.Minute).Unix()) reset.LastRequestTime = int(time.Now().Unix()) reset.UserId = user.Id if reset.Token == "" { // 如果 Token 为空,说明是第一次请求,插入新记录 reset.Token = token err = dbdata.Add(reset) } else { // 如果 Token 不为空,说明记录已存在,更新记录 reset.Token = token err = dbdata.Update("user_id", reset.UserId, reset) } if err != nil { RespError(w, RespInternalErr, "更新重置记录失败") return } // 获取邮箱服务器的配置 dataSmtp := &dbdata.SettingSmtp{} serverConf := &dbdata.SettingOther{} mail_err := dbdata.SettingGet(dataSmtp) server_err := dbdata.SettingGet(serverConf) if server_err != nil { RespError(w, RespInternalErr, "获取服务器配置失败,请检查后台对外地址的配置") return } if mail_err != nil { RespError(w, RespInternalErr, "获取邮箱配置失败,请检查邮箱配置") return } // 构建重置链接 parsedURL, err := url.Parse(serverConf.LinkAddr) if err != nil { RespError(w, RespInternalErr, "解析URL失败,可能是后台配置的对外地址不符合要求") return } scheme := parsedURL.Scheme hosts := parsedURL.Hostname() fullURL := fmt.Sprintf("%s://%s%s", scheme, hosts, base.Cfg.AdminAddr) resetLink := fmt.Sprintf("%s/ui/#resetPassword?token=%s", fullURL, reset.Token) // 发送邮件 mail_user := dataSmtp.Username password := dataSmtp.Password host := dataSmtp.Host port := strconv.Itoa(dataSmtp.Port) toUser := req.Email message := fmt.Sprintf("这个是vpn账号的重置链接:%s \n密码有效期%d分钟,超时请重新提交", resetLink, reset_interval_time) if err := SendResetMail(mail_user, password, toUser, "vpn密码重置", message, host, port, false); err != nil { RespError(w, RespInternalErr, "邮箱发送失败") return } RespSucess(w, "邮箱发送成功") } func SendResetMail(email, password, toEmail, subject, body, host, port string, isHtml bool) (err error) { header := make(map[string]string) header["From"] = "<" + email + ">" header["To"] = toEmail header["Subject"] = subject if isHtml { header["Content-Type"] = "text/html; charset=UTF-8" } else { header["Content-Type"] = "text/plain; charset=UTF-8" } message := "" for k, v := range header { message += fmt.Sprintf("%s: %s\r\n", k, v) } message += "\r\n" + body auth := smtp.PlainAuth( "", email, password, host, ) toEmails := strings.Split(toEmail, ";") fmt.Println(email, password, toEmails, host, port) err = sendMailUsingTLS( fmt.Sprintf("%s:%s", host, port), auth, email, toEmails, []byte(message), ) if err != nil { fmt.Printf("send_mail_error: %v, %v", toEmails, err) } return } // return a smtp client func dial(addr string) (*smtp.Client, error) { conn, err := tls.Dial("tcp", addr, nil) if err != nil { fmt.Println("Dialing Error:", err) return nil, err } //分解主机端口字符串 host, _, _ := net.SplitHostPort(addr) return smtp.NewClient(conn, host) } func sendMailUsingTLS(addr string, auth smtp.Auth, from string, to []string, msg []byte) (err error) { //create smtp client c, err := dial(addr) if err != nil { fmt.Println("Create smpt client error:", err) return err } defer c.Close() if auth != nil { if ok, _ := c.Extension("AUTH"); ok { if err = c.Auth(auth); err != nil { fmt.Println("Error during AUTH", err) return err } } } if err = c.Mail(from); err != nil { return err } for _, addr := range to { if err = c.Rcpt(addr); err != nil { return err } } w, err := c.Data() if err != nil { return err } _, err = w.Write(msg) if err != nil { return err } err = w.Close() if err != nil { return err } return c.Quit() }