Skip to content

Commit

Permalink
Login page (#1094)
Browse files Browse the repository at this point in the history
* feat: Add a login page

* feat: Modify save rules, more secure

* remove remoteAddr == "localhost"

* "登录失败次数过多,请等待 %d 分钟后再试

* cookie remove secure

* set cookie expires time by `NotAllowWanAccess`

* prettier

* fix: rename

* feat: auto login if unfilled

* feat: auto login if there is no username/password

* auto login if no username/password
  • Loading branch information
jeessy2 committed Apr 27, 2024
1 parent 76f5e35 commit 63b510b
Show file tree
Hide file tree
Showing 14 changed files with 457 additions and 164 deletions.
18 changes: 10 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,16 @@ func faviconFsFunc(writer http.ResponseWriter, request *http.Request) {

func runWebServer() error {
// 启动静态文件服务
http.HandleFunc("/static/", web.BasicAuth(staticFsFunc))
http.HandleFunc("/favicon.ico", web.BasicAuth(faviconFsFunc))

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("/webhookTest", web.BasicAuth(web.WebhookTest))
http.HandleFunc("/static/", web.AuthAssert(staticFsFunc))
http.HandleFunc("/favicon.ico", web.AuthAssert(faviconFsFunc))
http.HandleFunc("/login", web.AuthAssert(web.Login))
http.HandleFunc("/loginFunc", web.AuthAssert(web.LoginFunc))

http.HandleFunc("/", web.Auth(web.Writing))
http.HandleFunc("/save", web.Auth(web.Save))
http.HandleFunc("/logs", web.Auth(web.Logs))
http.HandleFunc("/clearLog", web.Auth(web.ClearLog))
http.HandleFunc("/webhookTest", web.Auth(web.WebhookTest))

util.Log("监听 %s", *listen)

Expand Down
2 changes: 2 additions & 0 deletions static/constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ const I18N_MAP = {
"Ipv4CmdHelp": "Get IPv4 through command, only use the first matching IPv4 address of standard output(stdout). Such as: ip -4 addr show eth1",
"Ipv6CmdHelp": "Get IPv6 through command, only use the first matching IPv6 address of standard output(stdout). Such as: ip -6 addr show eth1",
"NetInterfaceEmptyHelp": '<span style="color: red">No available network card found</span>',
"Login": 'Login',
},
'zh-cn': {
'Logs': '日志',
Expand Down Expand Up @@ -287,5 +288,6 @@ const I18N_MAP = {
<a target="blank" href="https://github.com/jeessy2/ddns-go/wiki/通过命令获取IP参考">点击参考更多</a>
`,
"NetInterfaceEmptyHelp": '<span style="color: red">没有找到可用的网卡</span>',
"Login": '登录',
}
};
22 changes: 22 additions & 0 deletions static/theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
function toggleTheme(write = false) {
const docEle = document.documentElement;
if (docEle.getAttribute("data-theme") === "dark") {
docEle.removeAttribute("data-theme");
write && localStorage.setItem("theme", "light");
} else {
docEle.setAttribute("data-theme", "dark");
write && localStorage.setItem("theme", "dark");
}
}

const theme = localStorage.getItem("theme") ??
(window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light");

if (theme === "dark") {
toggleTheme();
}

// 主题切换
document.getElementById("themeButton").addEventListener('click', () => toggleTheme(true));
6 changes: 6 additions & 0 deletions static/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ const request = {
},
get: async function(path, data, parseFunc) {
const response = await fetch(`${this.baseURL}${path}?${this.stringify(data)}`)
if (response.redirected) {
window.location.href = response.url
}
return await (parseFunc||this.parse)(response)
},
post: async function(path, data, parseFunc) {
Expand All @@ -105,6 +108,9 @@ const request = {
method: 'POST',
body: data
})
if (response.redirected) {
window.location.href = response.url
}
return await (parseFunc||this.parse)(response)
}
}
14 changes: 9 additions & 5 deletions util/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,10 @@ func init() {
message.SetString(language.English, "Callback调用失败, 异常信息: %s", "Webhook called failed! Exception: %s")

// save
message.SetString(language.English, "若通过公网访问, 仅允许在ddns-go启动后 5 分钟内完成首次配置", "If accessed via the public network, only allow the first configuration to be completed within 5 minutes after ddns-go starts")
message.SetString(language.English, "若从未设置过帐号密码, 仅允许在ddns-go启动后 5 分钟内设置, 请重启ddns-go", "If you have never set an account password, you can only set it within 5 minutes after ddns-go starts, please restart ddns-go")
message.SetString(language.English, "启用外网访问, 必须输入登录用户名/密码", "Enable external network access, you must enter the login username/password")
message.SetString(language.English, "修改 '通过命令获取' 必须设置帐号密码,请先设置帐号密码", "Modify 'Get by command' must set username/password, please set username/password first")
message.SetString(language.English, "密码不安全!尝试使用更长的密码", "insecure password, try using a longer password")
message.SetString(language.English, "请在ddns-go启动后 5 分钟内完成首次配置", "Please complete the first configuration within 5 minutes after ddns-go starts")
message.SetString(language.English, "之前未设置帐号密码, 仅允许在ddns-go启动后 5 分钟内设置, 请重启ddns-go", "The username/password has not been set before, only allowed to set within 5 minutes after ddns-go starts, please restart ddns-go")
message.SetString(language.English, "必须输入登录用户名/密码", "Must enter login username/password")
message.SetString(language.English, "密码不安全!尝试使用更复杂的密码", "Password is not secure! Try using a more complex password")
message.SetString(language.English, "数据解析失败, 请刷新页面重试", "Data parsing failed, please refresh the page and try again")
message.SetString(language.English, "第 %s 个配置未填写域名", "The %s config does not fill in the domain")

Expand Down Expand Up @@ -113,6 +112,11 @@ func init() {
message.SetString(language.English, "失败", "failed")
message.SetString(language.English, "成功", "success")

// Login
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")

}

func Log(key string, args ...interface{}) {
Expand Down
8 changes: 0 additions & 8 deletions util/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@ func IsPrivateNetwork(remoteAddr string) bool {
ip.IsLinkLocalUnicast() // 169.254/16, fe80::/10
}

// localhost
if remoteAddr == "localhost" {
return true
}
// private domain eg. .cluster.local
if strings.HasSuffix(remoteAddr, ".local") {
return true
}
return false
}

Expand Down
31 changes: 31 additions & 0 deletions util/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package util

import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"math/rand"
"time"
)

// GenerateToken 生成Token
func GenerateToken(username string) string {
key := []byte(generateRandomKey())
h := hmac.New(sha256.New, key)
msg := fmt.Sprintf("%s%d", username, time.Now().Unix())
h.Write([]byte(msg))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

// generateRandomKey 生成随机密钥
func generateRandomKey() string {
// 设置随机种子
source := rand.NewSource(time.Now().UnixNano())
random := rand.New(source)

// 生成随机的64位整数
randomNumber := random.Uint64()

return fmt.Sprint(randomNumber)
}
70 changes: 70 additions & 0 deletions web/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package web

import (
"net/http"
"time"

"github.com/jeessy2/ddns-go/v6/config"
"github.com/jeessy2/ddns-go/v6/util"
)

// ViewFunc func
type ViewFunc func(http.ResponseWriter, *http.Request)

// Auth 验证Token是否已经通过
func Auth(f ViewFunc) ViewFunc {
return func(w http.ResponseWriter, r *http.Request) {
tokenInCookie, err := r.Cookie("token")
if err != nil {
http.Redirect(w, r, "./login", http.StatusTemporaryRedirect)
return
}

conf, _ := config.GetConfigCached()

// 禁止公网访问
if conf.NotAllowWanAccess {
if !util.IsPrivateNetwork(r.RemoteAddr) {
w.WriteHeader(http.StatusForbidden)
util.Log("%q 被禁止从公网访问", util.GetRequestIPStr(r))
return
}
}

// 验证token
if tokenInSystem != "" && tokenInSystem == tokenInCookie.Value {
f(w, r) // 执行被装饰的函数
return
}

http.Redirect(w, r, "./login", http.StatusTemporaryRedirect)
}
}

// AuthAssert 保护静态等文件不被公网访问
func AuthAssert(f ViewFunc) ViewFunc {
return func(w http.ResponseWriter, r *http.Request) {

conf, err := config.GetConfigCached()

// 配置文件为空, 启动时间超过3小时禁止从公网访问
if err != nil &&
time.Now().Unix()-startTime > 3*60*60 && !util.IsPrivateNetwork(r.RemoteAddr) {
w.WriteHeader(http.StatusForbidden)
util.Log("%q 配置文件为空, 超过3小时禁止从公网访问", util.GetRequestIPStr(r))
return
}

// 禁止公网访问
if conf.NotAllowWanAccess {
if !util.IsPrivateNetwork(r.RemoteAddr) {
w.WriteHeader(http.StatusForbidden)
util.Log("%q 被禁止从公网访问", util.GetRequestIPStr(r))
return
}
}

f(w, r) // 执行被装饰的函数

}
}
96 changes: 0 additions & 96 deletions web/basic_auth.go

This file was deleted.

Loading

0 comments on commit 63b510b

Please sign in to comment.