mirror of
https://github.com/jeessy2/ddns-go.git
synced 2025-10-20 15:33:46 +08:00
feat: 登陆失败超过5次, 延时60s响应 (#138)
This commit is contained in:
@ -1,72 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"ddns-go/util"
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// User 登录用户
|
||||
type User struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// ViewFunc func
|
||||
type ViewFunc func(http.ResponseWriter, *http.Request)
|
||||
|
||||
// BasicAuth basic auth
|
||||
func BasicAuth(f ViewFunc) ViewFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
conf, _ := GetConfigCache()
|
||||
|
||||
// 禁止公网访问
|
||||
if conf.NotAllowWanAccess {
|
||||
if !util.IsPrivateNetwork(r.RemoteAddr) || !util.IsPrivateNetwork(r.Host) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 帐号或密码为空。跳过
|
||||
if conf.Username == "" && conf.Password == "" {
|
||||
// 执行被装饰的函数
|
||||
f(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// 认证帐号密码
|
||||
basicAuthPrefix := "Basic "
|
||||
|
||||
// 获取 request header
|
||||
auth := r.Header.Get("Authorization")
|
||||
// 如果是 http basic auth
|
||||
if strings.HasPrefix(auth, basicAuthPrefix) {
|
||||
// 解码认证信息
|
||||
payload, err := base64.StdEncoding.DecodeString(
|
||||
auth[len(basicAuthPrefix):],
|
||||
)
|
||||
if err == nil {
|
||||
pair := bytes.SplitN(payload, []byte(":"), 2)
|
||||
if len(pair) == 2 &&
|
||||
bytes.Equal(pair[0], []byte(conf.Username)) &&
|
||||
bytes.Equal(pair[1], []byte(conf.Password)) {
|
||||
// 执行被装饰的函数
|
||||
f(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Printf("%s 登陆失败!\n", r.RemoteAddr)
|
||||
}
|
||||
|
||||
// 认证失败,提示 401 Unauthorized
|
||||
// Restricted 可以改成其他的值
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
// 401 状态码
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
log.Printf("%s 请求登陆!\n", r.RemoteAddr)
|
||||
}
|
||||
}
|
||||
|
18
main.go
18
main.go
@ -80,13 +80,13 @@ func run(firstDelay time.Duration) {
|
||||
http.Handle("/static/", http.FileServer(http.FS(staticEmbededFiles)))
|
||||
http.Handle("/favicon.ico", http.FileServer(http.FS(faviconEmbededFile)))
|
||||
|
||||
http.HandleFunc("/", config.BasicAuth(web.Writing))
|
||||
http.HandleFunc("/save", config.BasicAuth(web.Save))
|
||||
http.HandleFunc("/logs", config.BasicAuth(web.Logs))
|
||||
http.HandleFunc("/clearLog", config.BasicAuth(web.ClearLog))
|
||||
http.HandleFunc("/ipv4NetInterface", config.BasicAuth(web.Ipv4NetInterfaces))
|
||||
http.HandleFunc("/ipv6NetInterface", config.BasicAuth(web.Ipv6NetInterfaces))
|
||||
http.HandleFunc("/webhookTest", config.BasicAuth(web.WebhookTest))
|
||||
http.HandleFunc("/", web.BasicAuth(web.Writing))
|
||||
http.HandleFunc("/save", web.BasicAuth(web.Save))
|
||||
http.HandleFunc("/logs", web.BasicAuth(web.Logs))
|
||||
http.HandleFunc("/clearLog", web.BasicAuth(web.ClearLog))
|
||||
http.HandleFunc("/ipv4NetInterface", web.BasicAuth(web.Ipv4NetInterfaces))
|
||||
http.HandleFunc("/ipv6NetInterface", web.BasicAuth(web.Ipv6NetInterfaces))
|
||||
http.HandleFunc("/webhookTest", web.BasicAuth(web.WebhookTest))
|
||||
|
||||
log.Println("监听", *listen, "...")
|
||||
|
||||
@ -98,7 +98,7 @@ func run(firstDelay time.Duration) {
|
||||
err := http.ListenAndServe(*listen, nil)
|
||||
|
||||
if err != nil {
|
||||
log.Println("启动端口发生异常, 1分钟后自动关闭DOS窗口", err)
|
||||
log.Println("启动端口发生异常, 请检查端口是否被占用", err)
|
||||
time.Sleep(time.Minute)
|
||||
os.Exit(1)
|
||||
}
|
||||
@ -128,7 +128,7 @@ func getService() service.Service {
|
||||
DisplayName: "ddns-go",
|
||||
Description: "简单好用的DDNS。自动更新域名解析到公网IP(支持阿里云、腾讯云dnspod、Cloudflare、华为云)",
|
||||
Arguments: []string{"-l", *listen, "-f", strconv.Itoa(*every), "-c", *configFilePath},
|
||||
Option: options,
|
||||
Option: options,
|
||||
}
|
||||
|
||||
prg := &program{}
|
||||
|
73
web/basic_auth.go
Normal file
73
web/basic_auth.go
Normal file
@ -0,0 +1,73 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"ddns-go/config"
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ViewFunc func
|
||||
type ViewFunc func(http.ResponseWriter, *http.Request)
|
||||
|
||||
type loginDetect struct {
|
||||
FailTimes int
|
||||
}
|
||||
|
||||
var ld = &loginDetect{}
|
||||
|
||||
// BasicAuth basic auth
|
||||
func BasicAuth(f ViewFunc) ViewFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
conf, _ := config.GetConfigCache()
|
||||
|
||||
// 帐号或密码为空。跳过
|
||||
if conf.Username == "" && conf.Password == "" {
|
||||
// 执行被装饰的函数
|
||||
f(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// 认证帐号密码
|
||||
basicAuthPrefix := "Basic "
|
||||
|
||||
// 获取 request header
|
||||
auth := r.Header.Get("Authorization")
|
||||
// 如果是 http basic auth
|
||||
if strings.HasPrefix(auth, basicAuthPrefix) {
|
||||
// 解码认证信息
|
||||
payload, err := base64.StdEncoding.DecodeString(
|
||||
auth[len(basicAuthPrefix):],
|
||||
)
|
||||
if err == nil {
|
||||
pair := bytes.SplitN(payload, []byte(":"), 2)
|
||||
if len(pair) == 2 &&
|
||||
bytes.Equal(pair[0], []byte(conf.Username)) &&
|
||||
bytes.Equal(pair[1], []byte(conf.Password)) {
|
||||
ld.FailTimes = 0
|
||||
// 执行被装饰的函数
|
||||
f(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ld.FailTimes = ld.FailTimes + 1
|
||||
if ld.FailTimes > 5 {
|
||||
log.Printf("%s 登陆失败超过5次! 并延时60s响应\n", r.RemoteAddr)
|
||||
time.Sleep(60 * time.Second)
|
||||
ld.FailTimes = 0
|
||||
}
|
||||
log.Printf("%s 登陆失败!\n", r.RemoteAddr)
|
||||
}
|
||||
|
||||
// 认证失败,提示 401 Unauthorized
|
||||
// Restricted 可以改成其他的值
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
// 401 状态码
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
log.Printf("%s 请求登陆!\n", r.RemoteAddr)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user