Skip to content

Commit

Permalink
feat: reset password (#1119)
Browse files Browse the repository at this point in the history
- Encrypt password using bcrypt
- Add reset password function
  • Loading branch information
jeessy2 committed May 19, 2024
1 parent 354c53f commit eda1050
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 26 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
- `-noweb` 不启动web服务
- `-skipVerify` 跳过证书验证
- `-dns` 自定义 DNS 服务器
- `-resetPassword` 重置密码
- [可选] 参考示例
- 10分钟同步一次, 并指定了配置文件地址
```bash
Expand All @@ -60,6 +61,10 @@
```bash
./ddns-go -s install -f 10 -cacheTimes 180
```
- 重置密码
```bash
./ddns-go -resetPassword 123456
```
- [可选] 使用 [Homebrew](https://brew.sh) 安装 [ddns-go](https://formulae.brew.sh/formula/ddns-go):

```bash
Expand Down
5 changes: 5 additions & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Automatically obtain your public IPv4 or IPv6 address and resolve it to the corr
- `-noweb` does not start web service
- `-skipVerify` skip certificate verification
- `-dns` custom DNS server
- `-resetPassword` reset password
- [Optional] Examples
- 10 minutes to synchronize once, and the configuration file address is specified
```bash
Expand All @@ -58,6 +59,10 @@ Automatically obtain your public IPv4 or IPv6 address and resolve it to the corr
```bash
./ddns-go -s install -f 10 -cacheTimes 180
```
- reset password
```bash
./ddns-go -resetPassword 123456
```
- [Optional] You can use [Homebrew](https://brew.sh) to install [ddns-go](https://formulae.brew.sh/formula/ddns-go)

```bash
Expand Down
50 changes: 49 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"errors"
"io"
"log"
"os"
Expand All @@ -12,6 +13,7 @@ import (
"sync"

"github.com/jeessy2/ddns-go/v6/util"
passwordvalidator "github.com/wagslane/go-password-validator"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -116,13 +118,22 @@ func GetConfigCached() (conf Config, err error) {
return *cache.ConfigSingle, err
}

// CompatibleConfig 兼容v5.0.0之前的配置文件
// CompatibleConfig 兼容之前的配置文件
func (conf *Config) CompatibleConfig() {
// 如配置文件不为空, 兼容之前的语言为中文
if conf.Lang == "" {
conf.Lang = "zh"
}

// 如果之前密码不为空且不是bcrypt加密后的密码, 把密码加密并保存
if conf.Password != "" && !util.IsHashedPassword(conf.Password) {
hashedPwd, err := util.HashPassword(conf.Password)
if err == nil {
conf.Password = hashedPwd
conf.SaveConfig()
}
}

// 兼容v5.0.0之前的配置文件
if len(conf.DnsConf) > 0 {
return
Expand Down Expand Up @@ -177,6 +188,43 @@ func (conf *Config) SaveConfig() (err error) {
return
}

// 重置密码
func (conf *Config) ResetPassword(newPassword string) {
// 初始化语言
util.InitLogLang(conf.Lang)

// 先检查密码是否安全
hashedPwd, err := conf.CheckPassword(newPassword)
if err != nil {
util.Log(err.Error())
return
}

// 保存配置
conf.Password = hashedPwd
conf.SaveConfig()
util.Log("用户名 %s 的密码已重置成功! 请重启ddns-go", conf.Username)
}

// CheckPassword 检查密码
func (conf *Config) CheckPassword(newPassword string) (hashedPwd string, err error) {
var minEntropyBits float64 = 50
if conf.NotAllowWanAccess {
minEntropyBits = 25
}
err = passwordvalidator.Validate(newPassword, minEntropyBits)
if err != nil {
return "", errors.New(util.LogStr("密码不安全!尝试使用更复杂的密码"))
}

// 加密密码
hashedPwd, err = util.HashPassword(newPassword)
if err != nil {
return "", errors.New(util.LogStr("异常信息: %s", err.Error()))
}
return
}

func (conf *DnsConfig) getIpv4AddrFromInterface() string {
ipv4, _, err := GetNetInterface()
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/wagslane/go-password-validator v0.3.0
golang.org/x/net v0.25.0
gopkg.in/yaml.v3 v3.0.1
golang.org/x/crypto v0.23.0
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I=
github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
24 changes: 19 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ var updateFlag = flag.Bool("u", false, "Upgrade ddns-go to the latest version")
var listen = flag.String("l", ":9876", "Listen address")

// 更新频率(秒)
var every = flag.Int("f", 300, "Sync frequency(seconds)")
var every = flag.Int("f", 300, "Update frequency(seconds)")

// 缓存次数
var ipCacheTimes = flag.Int("cacheTimes", 5, "Interval N times compared with service providers")
var ipCacheTimes = flag.Int("cacheTimes", 5, "Cache times")

// 服务管理
var serviceType = flag.String("s", "", "Service management (install|uninstall|restart)")

// 配置文件路径
var configFilePath = flag.String("c", util.GetConfigFilePathDefault(), "config file path")
var configFilePath = flag.String("c", util.GetConfigFilePathDefault(), "Custom configuration file path")

// Web 服务
var noWebService = flag.Bool("noweb", false, "No web service")
Expand All @@ -51,7 +51,10 @@ var noWebService = flag.Bool("noweb", false, "No web service")
var skipVerify = flag.Bool("skipVerify", false, "Skip certificate verification")

// 自定义 DNS 服务器
var customDNS = flag.String("dns", "", "Custom DNS server, example: 8.8.8.8")
var customDNS = flag.String("dns", "", "Custom DNS server address, example: 8.8.8.8")

// 重置密码
var newPassword = flag.String("resetPassword", "", "Reset password to the one entered")

//go:embed static
var staticEmbeddedFiles embed.FS
Expand All @@ -72,17 +75,28 @@ func main() {
update.Self(version)
return
}
// 检查监听地址
if _, err := net.ResolveTCPAddr("tcp", *listen); err != nil {
log.Fatalf("Parse listen address failed! Exception: %s", err)
}
// 设置版本号
os.Setenv(web.VersionEnv, version)
// 设置配置文件路径
if *configFilePath != "" {
absPath, _ := filepath.Abs(*configFilePath)
os.Setenv(util.ConfigFilePathENV, absPath)
}
// 重置密码
if *newPassword != "" {
conf, _ := config.GetConfigCached()
conf.ResetPassword(*newPassword)
return
}
// 设置跳过证书验证
if *skipVerify {
util.SetInsecureSkipVerify()
}
// 设置自定义DNS
if *customDNS != "" {
util.SetDNS(*customDNS)
}
Expand Down Expand Up @@ -118,7 +132,7 @@ func main() {
}

func run() {
// 兼容v5.0.0之前的配置文件
// 兼容之前的配置文件
conf, _ := config.GetConfigCached()
conf.CompatibleConfig()
// 初始化语言
Expand Down
2 changes: 2 additions & 0 deletions static/constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ const I18N_MAP = {
'NotAllowWanAccessHelp': 'Default enabled, can prohibit access to this page from the public network',
'Username': 'Username',
'accountHelp': 'Please enter to protect your information security',
'passwordHelp': 'If you need to change the password, please enter it here',
'Password': 'Password',
'WebhookURLHelp': `
<a
Expand Down Expand Up @@ -264,6 +265,7 @@ const I18N_MAP = {
'NotAllowWanAccessHelp': '默认启用, 可禁止从公网访问本页面',
'Username': '用户名',
'accountHelp': '为保护你的信息安全,建议输入',
'passwordHelp': '如需修改密码,请在此处输入新密码',
'Password': '密码',
'WebhookURLHelp': `
<a target="blank" href="https://github.com/jeessy2/ddns-go#webhook">点击参考官方 Webhook 说明</a>
Expand Down
29 changes: 29 additions & 0 deletions util/bcrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package util

import (
"golang.org/x/crypto/bcrypt"
)

// HashPassword 密码哈希
func HashPassword(password string) (string, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashedPassword), nil
}

// PasswordOK 检查密码
func PasswordOK(hashedPassword, password string) bool {
if hashedPassword == "" && password == "" {
return true
}
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return err == nil
}

// IsHashedPassword 是否是哈希密码
func IsHashedPassword(password string) bool {
_, err := bcrypt.Cost([]byte(password))
return err == nil
}
1 change: 1 addition & 0 deletions util/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func init() {
message.SetString(language.English, "%q 登陆成功", "%q login successfully")
message.SetString(language.English, "用户名或密码错误", "Username or password is incorrect")
message.SetString(language.English, "登录失败次数过多,请等待 %d 分钟后再试", "Too many login failures, please try again after %d minutes")
message.SetString(language.English, "用户名 %s 的密码已重置成功! 请重启ddns-go", "The password of username %s has been reset successfully! Please restart ddns-go")

}

Expand Down
2 changes: 1 addition & 1 deletion web/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func LoginFunc(w http.ResponseWriter, r *http.Request) {
conf, _ := config.GetConfigCached()

// 登陆成功
if data.Username == conf.Username && data.Password == conf.Password {
if data.Username == conf.Username && util.PasswordOK(conf.Password, data.Password) {
ld.ticker.Stop()
ld.failedTimes = 0
tokenInSystem = util.GenerateToken(data.Username)
Expand Down
25 changes: 10 additions & 15 deletions web/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/jeessy2/ddns-go/v6/config"
"github.com/jeessy2/ddns-go/v6/dns"
"github.com/jeessy2/ddns-go/v6/util"
passwordvalidator "github.com/wagslane/go-password-validator"
)

var startTime = time.Now().Unix()
Expand Down Expand Up @@ -67,29 +66,25 @@ func checkAndSave(request *http.Request) string {
}

conf.NotAllowWanAccess = data.NotAllowWanAccess
conf.Username = usernameNew
conf.Password = passwordNew
conf.WebhookURL = strings.TrimSpace(data.WebhookURL)
conf.WebhookRequestBody = strings.TrimSpace(data.WebhookRequestBody)
conf.WebhookHeaders = strings.TrimSpace(data.WebhookHeaders)

// 如果新密码不为空则检查是否够强, 内/外网要求强度不同
conf.Username = usernameNew
if passwordNew != "" {
hashedPwd, err := conf.CheckPassword(passwordNew)
if err != nil {
return err.Error()
}
conf.Password = hashedPwd
}

// 帐号密码不能为空
if conf.Username == "" || conf.Password == "" {
return util.LogStr("必须输入登录用户名/密码")
}

// 如果密码不为空则检查是否够强, 内/外网要求强度不同
if conf.Password != "" {
var minEntropyBits float64 = 50
if conf.NotAllowWanAccess {
minEntropyBits = 25
}
err = passwordvalidator.Validate(conf.Password, minEntropyBits)
if err != nil {
return util.LogStr("密码不安全!尝试使用更复杂的密码")
}
}

dnsConfFromJS := data.DnsConf
var dnsConfArray []config.DnsConfig
empty := dnsConf4JS{}
Expand Down
4 changes: 2 additions & 2 deletions web/writing.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ func Writing(writer http.ResponseWriter, request *http.Request) {
err = tmpl.Execute(writer, struct {
DnsConf template.JS
NotAllowWanAccess bool
config.User
Username string
config.Webhook
Version string
Ipv4 []config.NetInterface
Ipv6 []config.NetInterface
}{
DnsConf: template.JS(getDnsConfStr(conf.DnsConf)),
NotAllowWanAccess: conf.NotAllowWanAccess,
User: conf.User,
Username: conf.User.Username,
Webhook: conf.Webhook,
Version: os.Getenv(VersionEnv),
Ipv4: ipv4,
Expand Down
3 changes: 1 addition & 2 deletions web/writing.html
Original file line number Diff line number Diff line change
Expand Up @@ -574,12 +574,11 @@ <h5 class="portlet__head">IPv6</h5>
type="password"
name="Password"
id="Password"
value="{{.Password}}"
autocomplete="new-password"
aria-describedby="passwordHelp"
/>
<small
data-i18n_html="accountHelp"
data-i18n_html="passwordHelp"
id="passwordHelp"
class="form-text text-muted"
></small>
Expand Down

0 comments on commit eda1050

Please sign in to comment.