anylink/server/admin/api_reset_password.go

296 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()
}